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之间转换

目录
相关文章
|
24天前
|
Java 调度
Java并发编程:深入理解线程池的原理与实践
【4月更文挑战第6天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将从线程池的基本原理入手,逐步解析其工作过程,以及如何在实际开发中合理使用线程池以提高程序性能。同时,我们还将关注线程池的一些高级特性,如自定义线程工厂、拒绝策略等,以帮助读者更好地掌握线程池的使用技巧。
|
1月前
|
缓存 算法 安全
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(二)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
18 0
|
1月前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
82 0
|
1月前
|
设计模式 缓存 Java
【Java技术专题】「入门到精通系列教程」深入探索Java特性中并发编程体系的原理和实战开发指南( 实现可伸缩IO专题)— 上
【Java技术专题】「入门到精通系列教程」深入探索Java特性中并发编程体系的原理和实战开发指南( 实现可伸缩IO专题)— 上
50 0
|
1月前
|
存储 安全 Java
【深度挖掘Java并发编程底层源码】「底层技术原理体系」带你零基础认识和分析学习相关的异步任务提交机制FutureTask的底层原理
【深度挖掘Java并发编程底层源码】「底层技术原理体系」带你零基础认识和分析学习相关的异步任务提交机制FutureTask的底层原理
14 0
|
1天前
|
存储 安全 Java
【Java EE】CAS原理和实现以及JUC中常见的类的使用
【Java EE】CAS原理和实现以及JUC中常见的类的使用
|
3天前
|
设计模式 消息中间件 Java
Java 设计模式:探索发布-订阅模式的原理与应用
【4月更文挑战第27天】发布-订阅模式是一种消息传递范式,被广泛用于构建松散耦合的系统。在 Java 中,这种模式允许多个对象监听和响应感兴趣的事件。
17 2
|
5天前
|
安全 Java 开发者
Java编程:深入探索其原理、特性与实战代码
Java编程:深入探索其原理、特性与实战代码
10 1
|
6天前
|
设计模式 Java 索引
由反射引出的Java动态代理与静态代理
由反射引出的Java动态代理与静态代理
12 0
|
8天前
|
Java API Spring
Java基础教程(13)-Java中的反射和动态代理
【4月更文挑战第13天】Java反射机制允许程序在运行时获取类的信息并调用其方法。Class类是基础,提供获取类属性和方法的能力。通过Class对象,可以操作实例字段和方法,如getField、getDeclaredField等。动态代理是Java提供的创建接口实例的机制,其中JDK动态代理需目标类实现接口,而Cglib则可代理未实现接口的类。动态代理涉及Proxy和InvocationHandler接口。