Java中动态代理使用与原理详解

简介: Java中动态代理使用与原理详解

本篇博文介绍的是JDK的动态代理,Java中动态代理不仅仅是JDK的动态代理还有CGLIB代理。


(JDK)动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。


即,不直接调用目标对象而是通过代理对象调用。代理对象不直接生成,而是程序运行时根据需要动态生成!


代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。代理对象的生成,是利用JDK 的API,动态的在内存中构建代理对象。 动态代理也叫做:JDK 代理、接口代理。

JDK 实现代理只需要使用newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )


代理设计模式的原理:


使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

动态代理之所以被称为动态,是因为运行时才将它的类创建出来。代码开始执行时,还没有proxy类,它是根据需要从传入的接口集创建的。


【1】Proxy


专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。提供了许多用于创建动态代理类和动态代理对象的静态方法。


① static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)


创建一个动态代理类所对应的Class对象。


② static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)


直接创建一个动态代理对象。


其他方法如下图:


【2】InvocationHandler


InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类的invoke方法,由它决定处理。


InvocationHandler接口源码如下:

 * //InvocationHandler是一个被代理实例的调用处理程序实现的接口。
 * // 每一个代理实例都有一个相关联的invocation handler
 * //当代理实例方法被调用时,代理会转发到相关联的invocation handler的invoke方法。
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}


InvocationHandler 内部只有一个 invoke() 方法,当一个代理实例的方法被调用时,与代理实例相关联的invocationHhandler的invoke方法将会被调用。正是这个方法决定了怎么样处理代理传递过来的方法调用。


proxy 代理对象

method 代理对象调用的方法

args 调用的方法中的参数

因为,Proxy 动态产生的代理对象会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。


故而,总的运行流程为:获取代理实例–>代理实例.方法 --> InvocationHandler.invoke() --> 接口真正实现类的方法。


InvocationHandler根本就不是proxy,它只是一个帮助proxy的类,proxy会被调用转发给它处理。Proxy本身是利用静态的Proxy.newProxyInstance()方法在运行时动态地创建的。



【3】动态代理步骤

① 父接口

父接口Human定义了两个方法:

interface Human {
  void info();
  void fly();
}



② 实现类SuperMan(被代理类)

// 被代理类
class SuperMan implements Human {
  public void info() {
    System.out.println("我是超人!我怕谁!");
  }
  public void fly() {
    System.out.println("I believe I can fly!");
  }
}



③ InvocationHandler实现类

也就是代理类的调用处理程序。

class MyInvocationHandler implements InvocationHandler {
  // 被代理类对象的声明
  Object obj;
  // 动态的创建一个代理类的对象
  public Object getProxyInstance()(Object obj){
    this.obj = obj;
    return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
        .getClass().getInterfaces(), this);
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    Object returnVal = method.invoke(obj, args);
    return returnVal;
  }
}



④ 测试方法

public static void main(String[] args) {
    //创建一个被代理类的对象
    SuperMan man = new SuperMan();
    //创建调用处理程序
    MyInvocationHandler handler = new MyInvocationHandler();
    //返回一个代理类的对象
    Object obj = handler.getProxyInstance()(man);
    System.out.println(obj.getClass());
    //代理实例 强转
    Human hu = (Human)obj;
    //通过代理类的对象调用重写的抽象方法
    hu.info();
    System.out.println();
    hu.fly();
  }

测试结果如下:

// 这里表明拿到的是代理对象
class com.web.test.$Proxy0
我是超人!我怕谁!
I believe I can fly!

【4】JDK动态代理原理

① 关键入口代码

Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), this);

根据类加载器,目标类的上行接口和InvocationHandler获取代理对象。这里有两个问题,第一如何获取?第二获取的代理对象是个什么样子?

先分析如何获取。


② 追踪源码到Proxy.newProxyInstance

     * //loader   定义代理类的类加载器
     * // interfaces  代理类需要实现的接口
     * // h -- 用来转发/反射目标方法的handler
     * //返回一个代理类的实例对象附带指定的代理类的invocation handler。
     * //该代理类被指定的类加载器定义且实现了指定的接口。
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException {
        Objects.requireNonNull(h)
         //...
         //寻找或者产生代理类的class
        Class<?> cl = getProxyClass0(loader, intfs);
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
      // 拿到代理类的构造器--这一步很关键!
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //如果非public,setAccessible(true);--反射中常见操作
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 通过构造器反射获取代理类实例对象(InvocationHandler作为参数)
            return cons.newInstance(new Object[]{h});
        }
        //...
    }

过程很清晰(参考代码注释),继续细究下去。


Class<?> cl = getProxyClass0(loader, intfs)做了什么?

源码如下:

//产生一个代理类 class。在调用该方法前必须调用checkProxyAccess 方法进行权限检查
 private static Class<?> getProxyClass0(ClassLoader loader,
                                        Class<?>... interfaces) {
     if (interfaces.length > 65535) {
         throw new IllegalArgumentException("interface limit exceeded");
     }
     //如果代理类已经存在,则直接返回缓存中的copy;
     //否则,通过ProxyClassFactory创建代理类
     return proxyClassCache.get(loader, interfaces);
 }

如果缓存中有,就直接返回proxy class;否则就要通过ProxyClassFactory获取。


这里的cache 是WeakCache,暂且不去理会,继续看ProxyClassFactory。


Proxy的静态内部类–Proxy$ProxyClassFactory源码如下:

// 通过给定的类加载器和接口,产生、定义并返回代理类。
private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // prefix for all proxy class names--代理类前缀
    private static final String proxyClassNamePrefix = "$Proxy";
    //代理类编号
    private static final AtomicLong nextUniqueNumber = new AtomicLong();
    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);           
       //...省略代码
        //定义代理类所在的包
        String proxyPkg = null;    
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
记录非公共代理接口的包,以便在同一包中定义代理类。
验证所有非公共代理接口都在同一个包中。
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            // 如果修饰符非public,则变为final
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }
        if (proxyPkg == null) {
            // if no non-public proxy interfaces, use com.sun.proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }
        long num = nextUniqueNumber.getAndIncrement();
        // 代理类的完整名字=包名+前缀+序号
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        /* Generate the specified proxy class.*/
        // 这里,产生指定的proxy class 字节数组!!!
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
        // 通过一个native方法获取proxy class
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } 
        // ... 省略代码
    }
}

从ProxyClassFactory中下面的方法可以看到具体生成字节流的方法是ProxyGenerator.generateProxyClass(..).。最后通过native方法生成Class对象。同时对class对象的包名称有一些规定比如命名为com.web.test$proxy0。


获取代理类代码如下:

public c

要想得到字节码实例我们需要先下载这部分字节流,然后通过反编译得到java代码。


用ProxyGenerator.generateProxyClass(..)方法生成字节流,然后写进硬盘.假设我把proxyName定义为Human$ProxyCode。

public class TestProxyJava {
 public static void generateClassFile(Class clazz,String proxyName) throws Exception
    {
        //根据类信息和提供的代理类名称,生成字节码
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName,new Class[]{clazz});
        String paths = clazz.getResource(".").getPath();  
        System.out.println(paths);  
        FileOutputStream out = null;     
        //保留到硬盘中  
        out = new FileOutputStream(paths+proxyName+".class");    
        out.write(classFile);    
        out.flush(); 
    }
 public static void main(String[] args) {
   try {
    generateClassFile(Human.class,"Human$ProxyCode");
  } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  }
}
}

获取到的代理类源码如下:

public final class Human$ProxyCode extends Proxy implements Human{
  private static Method m1;
  private static Method m3;
  private static Method m4;
  private static Method m2;
  private static Method m0;
  public Human$ProxyCode(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
//final fly
  public final void fly()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  public final void info()
    throws 
  {
    try
    {
    /调用InvokeHandler的invoke方法,并将具体方法传了进去
      this.h.invoke(this, m4, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("com.web.test.Human").getMethod("fly", new Class[0]);
      m4 = Class.forName("com.web.test.Human").getMethod("info", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

可以发现这个类extends Proxy实现了我们需要代理的接口Human,且他的构造函数确实是需要传递一个InvocationHandler对象。


那么现在的情况就是我们的生成了一个代理类,这个代理类是我们需要代理的接口的实现类。我们的接口中定义的info和fly方法,在这个代理类中帮我们实现了,并且全部变成了final的。同时覆盖了一些Object类中的方法。


以info这个方法举例,方法中会调用InvocationHandler类中的invoke方法(也就是我们实现的逻辑的地方),同时把自己的Method对象,参数列表等传入进去。


再回头看上面的测试main方法:

public static void main(String[] args) {
  SuperMan man = new SuperMan();//创建一个被代理类的对象
  MyInvocationHandler handler = new MyInvocationHandler();
  Object obj = handler.bind(man);//返回一个代理类的对象
  System.out.println(obj.getClass());
  // 现在知道为什么可以强转了吧
  Human hu = (Human)obj;
  //通过代理类的对象调用重写的抽象方法
  hu.info();
  System.out.println();
  hu.fly();
}

【5】动态代理与AOP


在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。


AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。


主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等等。


简而言之,在不改变原有代码基础上进行功能增强!


添加HumanUtil模拟增强方法:

class HumanUtil {
  public void method1() {
    System.out.println("=======方法一=======");
  }
  public void method2() {
    System.out.println("=======方法二=======");
  }
}

修改MyInvocationHandler如下:

class MyInvocationHandler implements InvocationHandler {
  // 被代理类对象的声明
  Object obj;
  // 动态的创建一个代理类的对象
  public Object bind(Object obj){
    this.obj = obj;
    return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
        .getClass().getInterfaces(), this);
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    HumanUtil h = new HumanUtil();
    h.method1();
    Object returnVal = method.invoke(obj, args);
    h.method2();
    return returnVal;
  }
}

再次测试如下:

class com.web.test.$Proxy0
=======方法一=======
我是超人!我怕谁!
=======方法二=======
=======方法一=======
I believe I can fly!
=======方法二=======

使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理。


这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理。


如下图所示:


【6】JDK动态代理总结


① 概率总结


① JDK动态代理只能代理有接口的类,并且只能代理接口方法,不能代理一般的类中自定义的方法


因为代理类是一个实现了被代理对象的上行接口的类,所以类必须有接口。而且不能代理被代理对象中自定义的方法!


② 提供了一个使用InvocationHandler作为参数的构造方法


在代理类中做一层包装,业务逻辑在invoke方法中实现。


③ 重写了Object类的equals、hashCode、toString


它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。


④ 在invoke方法中我们甚至可以不用Method.invoke方法调用实现类就返回


这种方式常常用在RPC框架中,在invoke方法中发起通信调用远端的接口等


② 动态代理在框架中的应用

① 在mybatis中的应用


如在MyBatis原理分析之获取SqlSession一文中获取executor 实例就应用了代理模式。在MyBatis原理分析之获取Mapper接口的代理对象也应用了java的动态代理思想。

③ 代理模式的几种变异体

① 远程代理

远程代理可以作为另一个JVM上对象的本地代表。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。

②虚拟代理

虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。


③ 缓存代理

缓存代理会维护之前创建的对象,当收到请求时,在可能的情况下返回缓存的对象。它也允许许多个客户共享结果,以减少计算或网络延迟。


④ 保护代理

保护代理可以根据客户的角色来决定是否允许客户访问特定的方法。所以保护代理可能只提供给客户部分接口。保护代理通常是由Java的动态代理技术实现。

⑤ 同步代理

在多线程的情况下为主题提供安全的访问。

⑥ 复杂隐藏代理

用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也称为外观代理(Facade Proxy),这不难理解。复杂隐藏代理和外观模式是不一样的,因为代理控制访问,而外观模式只提供另一组接口。



【7】获取代理类class文件的简单方法


在【4】中通过ProxyGenerator.generateProxyClass(proxyName,new Class[]{clazz});获取class文件,比较繁琐,这里介绍一种简单方式。


关键代码如下:

生成$Proxy0的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

修改main方法如下:

public static void main(String[] args) {
    //生成$Proxy0的class文件
      System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    SuperMan man = new SuperMan();//创建一个被代理类的对象
    MyInvocationHandler handler = new MyInvocationHandler();
    Object obj = handler.bind(man);//返回一个代理类的对象
    System.out.println(obj.getClass());
    Human hu = (Human)obj;
    hu.info();//通过代理类的对象调用重写的抽象方法
    System.out.println();
    hu.fly();
  }


生成的$Proxy0路径在项目根目录下com/web/test–该包名为测试代码所在的包。

总结:


java动态代理是利用反射机制生成一个实现代理接口的代理类,在调用具体方法前调用内部InvokeHandler来处理,InvokeHandler将会调用invoke方法并处理目标实际方法。


而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。


如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP

如果目标对象实现了接口,可以强制使用CGLIB实现AOP

如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

目录
相关文章
|
11天前
|
存储 算法 Java
Java HashSet:底层工作原理与实现机制
本文介绍了Java中HashSet的工作原理,包括其基于HashMap实现的底层机制。通过示例代码展示了HashSet如何添加元素,并解析了add方法的具体过程,包括计算hash值、处理碰撞及扩容机制。
|
2月前
|
算法 Java
JAVA并发编程系列(8)CountDownLatch核心原理
面试中的编程题目“模拟拼团”,我们通过使用CountDownLatch来实现多线程条件下的拼团逻辑。此外,深入解析了CountDownLatch的核心原理及其内部实现机制,特别是`await()`方法的具体工作流程。通过详细分析源码与内部结构,帮助读者更好地理解并发编程的关键概念。
|
2天前
|
存储 Java 关系型数据库
高效连接之道:Java连接池原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。频繁创建和关闭连接会消耗大量资源,导致性能瓶颈。为此,Java连接池技术通过复用连接,实现高效、稳定的数据库连接管理。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接池的基本操作、配置和使用方法,以及在电商应用中的具体应用示例。
14 5
|
2天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
6天前
|
存储 安全 Java
深入理解Java中的FutureTask:用法和原理
【10月更文挑战第28天】`FutureTask` 是 Java 中 `java.util.concurrent` 包下的一个类,实现了 `RunnableFuture` 接口,支持异步计算和结果获取。它可以作为 `Runnable` 被线程执行,同时通过 `Future` 接口获取计算结果。`FutureTask` 可以基于 `Callable` 或 `Runnable` 创建,常用于多线程环境中执行耗时任务,避免阻塞主线程。任务结果可通过 `get` 方法获取,支持阻塞和非阻塞方式。内部使用 AQS 实现同步机制,确保线程安全。
|
5天前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
[Java]静态代理与动态代理(基于JDK1.8)
|
11天前
|
开发框架 Java 程序员
揭开Java反射的神秘面纱:从原理到实战应用!
本文介绍了Java反射的基本概念、原理及应用场景。反射允许程序在运行时动态获取类的信息并操作其属性和方法,广泛应用于开发框架、动态代理和自定义注解等领域。通过反射,可以实现更灵活的代码设计,但也需注意其性能开销。
29 1
|
22天前
|
算法 Java 开发者
Java中的垃圾回收机制:从原理到实践
Java的垃圾回收机制(Garbage Collection, GC)是其语言设计中的一大亮点,它为开发者提供了自动内存管理的功能,大大减少了内存泄漏和指针错误等问题。本文将深入探讨Java GC的工作原理、不同垃圾收集器的种类及它们各自的优缺点,并结合实际案例展示如何调优Java应用的垃圾回收性能,旨在帮助读者更好地理解和有效利用Java的这一特性。
|
27天前
|
网络协议 安全 Java
Java Socket原理
Java Socket原理是指在Java中通过Socket实现的网络通信的基础理论与机制。Socket是网络中不同设备间通信的一种标准方式,它允许应用程序之间通过TCP/IP等协议进行数据交换。在Java中,利用Socket编程可以方便地创建客户端与服务器端应用,实现跨网络的数据传输功能,是互联网软件开发中的重要技术之一。它支持多种通信模式,如可靠的流式套接字(TCP)和数据报式套接字(UDP)。
|
20天前
|
Java
深入理解Java动态代理
深入理解Java动态代理
16 1