快速深入理解JDK动态代理原理

点击上方“后端开发技术”,选择“设为星标” ,质资源及时送达快速深入理解JDK动态代理原理

代理模式在 Java 框架中有着极为广泛的应用,动态代理更是面试的重点,掌握 JDK动态代理及其原理,看这一篇文章就够了!

代理模式

什么是代理模式

为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。

说人话,就是代理不实现真正的业务逻辑,真正的逻辑由被代理对象实现,但是代理对象会对调用方法前后做增强以及控制处理。

在计算机中,一个问题如果不能直接解决,通常可以考虑增加中间层的办法,代理模式也是如此。

快速深入理解JDK动态代理原理

代理模式

比如上图,ProxyImage 只是实现了Image接口,其真正的display()逻辑有ProxyImage调用RealImage中的display()实现。

意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。


策略、代理、装饰器模式区别

策略模式:重点在于避免繁琐的ifelse判断,重点在于执行不同的策略,策略的执行类并不需要实现策略接口。

装饰器模式:重点在于如果需要扩展子类实现,并且原有功能不删减,不需要以继承的方式写很多子类。并且会实现统一的接口,而策略模式不需要,这样的好处是装饰类和被装饰类可以独立发展,不会相互耦合。

代理模式:装饰器模式为了增强功能,扩展后的对象仍是是对象本身。而代理模式是为了加以控制,偏重因自己无法完成或自己无需关心,需要他人干涉事件流程。装饰器模式偏重对原对象功能的扩展。


静态代理


创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。

接口:

public interface HelloInterface {
    void sayHello();}

被代理类:

public class Hello implements HelloInterface{
    @Override
    public void sayHello() {
        System.out.println("Hello zhanghao!");
    }
}

代理类:

public class HelloProxy implements HelloInterface{

    private HelloInterface helloInterface = new Hello();
    @Override
    public void sayHello() {
        System.out.println("Before invoke sayHello" );
        helloInterface.sayHello();
        System.out.println("After invoke sayHello");
    }
}

代理类调用:被代理类被传递给了代理类HelloProxy,代理类在执行具体方法时通过所持用的被代理类完成调用。

public static void main(String[] args) {
HelloProxy helloProxy = new HelloProxy();
helloProxy.sayHello();
}
输出:
Before invoke sayHello
Hello zhanghao!
After invoke sayHello

快速深入理解JDK动态代理原理

静态代理

使用静态代理很容易就完成了对一个类的代理操作。但是静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。

所以我们需要动态代理来解决这个问题。


动态代理


动态代理利用反射机制在运行时创建代理类。动态代理机制在 Java 中有着广泛的应用,例如,Spring AOP、Dubbo、MyBatis、Hibernate 等常用的开源框架,都使用到了动态代理机制。


实现猜想

试想如果是你会怎么实现动态代理?

我们先设定几个角色:被代理对象、被代理接口、代理类。

被代理对象是拥有不同的业务实现的。因为不想写重复的代码,假设代理对象需要是万能的,他可以代理不同的被代理类,也就是说它可以实现不同的被代理接口。对应的策略只能是根据不同接口,动态的生成代理类的Class对象,这就需要我们去动态生成字节码文件。


这样就生成了多个不同实现类,但是他们的核心控制逻辑是一样的,比如调用前后去打一行日志,我们并不能在每个生产类都去生成这样的代码,实在太丑陋了,这就需要增加一个中间层,再代理一次,被代理对象作为他的成员变量,由他真正去实现代理的控制逻辑。


但是这样再次面临那个问题,不同的接口如何在一个方法内部实现动态调用呢,那就想到了Java的反射,需要method、object、args。这样我们再次用增加中间层的方式解决了这个问题。

以上述逻辑为构想,得出了以下结构。

快速深入理解JDK动态代理原理

代理模式实现猜想

对应代码如下:

interface Subject{
  do();
}
class RealSubject implements Subject{
  do(){
    System.out.println("real sub A");
  }
}
// 中间层,控制类
Class ProxyMid implement InvokeInterface{
  Object obj;
  invoke(Method method,Object[] Args){
    //前置处理
    method.invoke(obj, args);
    //后置处理
  }
  ProxyMid(Object obj){
    this.obj = obj;
  }
  
}
// 动态生成的class
class DinamicClass implements iA or iB or iC{
  InvokeInterface proxy;
  InvokerClass(InvokeInterface proxy){
    this.proxy = proxy;
  }
  invoke(){
    proxy.invoke();
  }

通过以上的描述,可以看出总体结构是比较简单的,这里的难点在于如何动态生产不同的字节码。


JDK 动态代理

JDK动态代理demo

JDK 动态代理的核心是InvocationHandler 接口。这里提供一个 InvocationHandler 的Demo 实现,代码如下:

public class JDKProxyTest implements InvocationHandler {

  private Object object;

  public JDKProxyTest(Object object) {
    this.object = object;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("动态代理 前置增强");
    Object res = method.invoke(object, args);
    System.out.println("动态代理 后置增强");
    return res;
  }

  public <T> getProxy() {
    return (T)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), object.getClass().getInterfaces(), this);
  }

  public static void main(String[] args) {
    MyProxy myProxy = new MyWork();
    JDKProxyTest proxy = new JDKProxyTest(myProxy);
    MyProxy proxyJDK = proxy.getProxy();
    proxyJDK.work();
  }

}

interface MyProxy{
 void work();
}

class MyWork implements MyProxy{

  @Override
  public void work() {
    System.out.println("work hard");
  }
}

对于需要相同代理逻辑的业务类,只需要提供一个 InvocationHandler 接口实现类即可。在 Java 运行的过程中,JDK会为每个 RealSubject 类动态生成相应的代理类并加载到 JVM 中,然后创建对应的代理实例对象,返回给上层调用者。

了解了 JDK 动态代理的基本使用之后,下面我们就来分析 JDK动态代理创建代理类的底层实现原理。不同JDK版本的 Proxy 类实现可能有细微差别,但核心思路不变,这里使用 1.8.0 版本的 JDK。


JDK动态代理底层实现

动态代理具体步骤:

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

既然生成代理对象是用的Proxy类的静态方newProxyInstance,那么我们就去它的源码里看一下它到底都做了些什么?


代码从Proxy.newProxyInstance()开始追踪起。

// Proxy
private static final Class<?>[] constructorParams = 
{ InvocationHandler.class };
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

{
        ……
        final Class<?>[] intfs = interfaces.clone();
        // 省略权限控制代码 
        ……
        //重点!生成代理类对象
        Class<?> cl = getProxyClass0(loader, intfs);
            ……
            // 使用指定的调用处理程序获取代理类的构造函数对象
            // 这里的构造函数就是 InvocationHandler 作为入参的构造函数
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //如果Class作用域为私有,通过 setAccessible 支持访问
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //获取Proxy Class构造函数,创建Proxy代理实例。入参为 InvocationHandler 的实现类
            return cons.newInstance(new Object[]{h});
…… 省略异常处理

上面代码展示了动态代理的主要逻辑,其核心在于生成动态代理类方法 getProxyClass0(),其他部分都是一些平平无奇的反射生成对象逻辑 。

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
    //如果接口数量大于65535,抛出非法参数错误
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    //如果指定接口的代理类已经存在与缓存中,则不用新创建,直接从缓存中取即可;
    //如果缓存中没有指定代理对象,则通过ProxyClassFactory来创建一个代理对象。
    return proxyClassCache.get(loader, interfaces);
}


proxyClassCache 是定义在 Proxy 类中的静态字段,主要用于缓存已经创建过的代理类,定义如下:

// 代理对象的缓存
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

WeakCache.get() 方法会首先尝试从缓存中查找代理类,如果查找不到,则会创建 Factory 对象并调用其 get() 方法获取代理类。Factory 是 WeakCache 中的内部类,Factory.get() 方法会调用 ProxyClassFactory.apply() 方法创建并加载代理类。

快速深入理解JDK动态代理原理

cache.get( )内部调用链路


ProxyClassFactory内部类创建、定义代理类,返回给定ClassLoader 和interfaces的代理类。ProxyClassFactory.apply() 方法首先会检测代理类需要实现的接口集合,然后确定代理类的名称,之后创建代理类并将其写入文件中,最后加载代理类,返回对应的 Class 对象用于后续的实例化代理类对象。该方法的具体实现如下:

private static final class ProxyClassFactory
    implements BiFunction<ClassLoaderClass<?>[], Class<?>>
{
    // 代理类的名字的前缀统一为“$Proxy”
    rivate static final String proxyClassNamePrefix = "$Proxy";

    // 每个代理类前缀后面都会跟着一个唯一的编号,如$Proxy0、$Proxy1、$Proxy2
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        // ... 对interfaces集合进行一系列检测(略)

        String proxyPkg = null;     // package to define proxy class in
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        // ... 选择定义代理类的包名(略)

        /*
         * Choose a name for the proxy class to generate.代理类名自增编号
         */

        long num = nextUniqueNumber.getAndIncrement();
       // 代理类的名称是通过包名、代理类名称前缀以及编号这三项组成的
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        /*
         * 生成指定代理类,并且保存字节码文件
         */

        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);

          // 加载代理类,并返回Class对象
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        // …… 省略 try catch
    }
}


ProxyGenerator.generateProxyClass() 方法会按照指定的名称和接口集合生成代理类的字节码,并根据条件决定是否保存到磁盘上。该方法的具体代码如下:

public static byte[] generateProxyClass(final String name,
       Class[] interfaces) {
    ProxyGenerator gen = new ProxyGenerator(name, interfaces);
    // 动态生成代理类的字节码,具体生成过程不再详细介绍,感兴趣的读者可以继续分析
    final byte[] classFile = gen.generateClassFile();
    // 如果saveGeneratedFiles值为true,会将生成的代理类的字节码保存到文件中
    if (saveGeneratedFiles) { 
        java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction() {
                public Void run() {
                    // 省略try/catch代码块
                    FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class");
                    file.write(classFile);
                    file.close();
                    return null;
                }
            }
       );
    }
    return classFile; // 返回上面生成的代理类的字节码
}


生成代理类字节码文件的generateClassFile方法:

private byte[] generateClassFile() {
    //下面一系列的addProxyMethod方法是将接口中的方法和Object中的方法添加到代理方法中(proxyMethod)
    this.addProxyMethod(hashCodeMethod, Object.class);
    this.addProxyMethod(equalsMethod, Object.class);
    this.addProxyMethod(toStringMethod, Object.class);
    Class[] var1 = this.interfaces;
    int var2 = var1.length;

    int var3;
    Class var4;
    //获得接口中所有方法并添加到代理方法中
    for(var3 = 0; var3 < var2; ++var3) {
        var4 = var1[var3];
        Method[] var5 = var4.getMethods();
        int var6 = var5.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            Method var8 = var5[var7];
            this.addProxyMethod(var8, var4);
        }
    }

    Iterator var11 = this.proxyMethods.values().iterator();
    List var12;
    while(var11.hasNext()) {
        var12 = (List)var11.next();
        checkReturnTypes(var12);
    }
    Iterator var15;
    try {
        //生成代理类的构造函数
        this.methods.add(this.generateConstructor());
        var11 = this.proxyMethods.values().iterator();

        while(var11.hasNext()) {
            var12 = (List)var11.next();
            var15 = var12.iterator();
                
            while(var15.hasNext()) {
                ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
                this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;"10));
                this.methods.add(var16.generateMethod());
            }
        }

        this.methods.add(this.generateStaticInitializer());
    } catch (IOException var10) {
        throw new InternalError("unexpected I/O Exception", var10);
    }

    if(this.methods.size() > 'uffff') {
        throw new IllegalArgumentException("method limit exceeded");
    } else if(this.fields.size() > 'uffff') {
        throw new IllegalArgumentException("field limit exceeded");
    } else {
       this.cp.getClass(dotToSlash(this.className));
       this.cp.getClass("java/lang/reflect/Proxy");
       var1 = this.interfaces;
       var2 = var1.length;

       for(var3 = 0; var3 < var2; ++var3) {
           var4 = var1[var3];
           this.cp.getClass(dotToSlash(var4.getName()));
       }

       this.cp.setReadOnly();
       ByteArrayOutputStream var13 = new ByteArrayOutputStream();
       DataOutputStream var14 = new DataOutputStream(var13);

       try {
         // 按照顺序将class文件内容写入到输出流中
           var14.writeInt(-889275714);
           var14.writeShort(0);
           var14.writeShort(49);
           this.cp.write(var14);
           var14.writeShort(this.accessFlags);
           var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
           var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
           var14.writeShort(this.interfaces.length);
           Class[] var17 = this.interfaces;
           int var18 = var17.length;

           for(int var19 = 0; var19 < var18; ++var19) {
               Class var22 = var17[var19];
               var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
           }

           var14.writeShort(this.fields.size());
           var15 = this.fields.iterator();

           while(var15.hasNext()) {
               ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
               var20.write(var14);
           }

           var14.writeShort(this.methods.size());
           var15 = this.methods.iterator();

           while(var15.hasNext()) {
               ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
               var21.write(var14);
           }

           var14.writeShort(0);
           return var13.toByteArray();
       } catch (IOException var9) {
           throw new InternalError("unexpected I/O Exception", var9);
       }
   }
}

上述是字节码内容的生成逻辑,比较繁琐,按照class文件的规则去编排就好,这里不做重点讲解。字节码生成后,调用defineClass0来解析字节码,生成了Proxy的Class对象。

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

// 加载代理类,并返回Class对象
return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);

到这里,类加载完毕,就可以用反射调用构造函数生成代理对象了。

动态代理流程图:

快速深入理解JDK动态代理原理

生成代理类流程


反编译代理类

了解了整个执行流程后,很自然产生好奇,动态生成的代理类对象是什么样的呢?

在ProxyGenerator.generateProxyClass函数中 saveGeneratedFiles定义如下,其指代是否保存生成的代理类class文件,默认false不保存。

在前面的示例中,我们修改了此系统变量:

System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles""true");

因此可以在磁盘中找到被动态生成的文件 ¥Proxy0,反编译代码如下。

final class $Proxy0 extends Proxy implements MyProxy {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
  
  static {
        try {
          // 初始化成员变量,这里直接记录代理类method,方便反射调用
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
          // 代理目标方法
            m3 = Class.forName("com.daley.test.MyProxy").getMethod("work");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

  //InvocationHandler 在父类Proxy中作为成员变量
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
  
  public final void work() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) throws  {
       …… 非重点省略
    }

    public final String toString() throws  {
         …… 非重点省略
    }

    public final int hashCode() throws  {
         …… 非重点省略
    }
}

可以从反编译的结果看出,在最开始我们的推倒思路是正确的,区别就是所有生成的代理类不仅实现了目标接口方法,还继承了 Proxy 类,InvocationHandler 作为代理类父类Proxy中的成员变量。

至此JDK 动态代理的基本使用以及核心原理就介绍完了。


总结

简单总结一下,JDK 动态代理的实现原理是动态创建代理类并通过指定类加载器进行加载,在创建代理对象时将InvocationHandler对象作为构造参数传入。当调用代理对象时,会调用 InvocationHandler.invoke() 方法,从而执行代理逻辑,并最终调用真正业务对象的相应方法。


这可能是你见过最全面的HashMap解读

2022-03-14

快速深入理解JDK动态代理原理

JVM线上问题排查思路小结

2021-12-19

快速深入理解JDK动态代理原理

Redis底层编码解读 — ZipList和IntSet

2021-12-06

快速深入理解JDK动态代理原理

如何靠Redis在面试中脱颖而出

2021-12-04

快速深入理解JDK动态代理原理

原文始发于微信公众号(后端开发技术):快速深入理解JDK动态代理原理

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/46976.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!