likes
comments
collection
share

动态代理是怎么 “动” 起来的?

作者站长头像
站长
· 阅读数 21

代理模式可以说是应用最为广泛的设计模式之一,同时也是其他一些设计模式的基础或组成部分。

在上篇文章 深入浅出 Retrofit 中,就是通过 动态代理 来实现具体的网络请求逻辑。本着刨根究底的原则,这篇文章来探究一下动态代理的技术原理。

静态代理

在这之前,先来看一下基础的 静态代理

接口类 ICount 和实现类 Counter

public interface ICount {
    void test1();
    void test2();
}

public class Counter implements ICount {

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

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

现在要加一个需求,统计 Counter 类中每个方法的耗时。本着开闭原则,对扩展开放,对修改关闭,最好不要去动 Counter 原本的逻辑,可以提供一个同样实现 ICount 的代理类 CounterProxy

public class CounterProxy implements ICount {

    private Counter counter;

    public CounterProxy(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void test1() {
        long start = System.currentTimeMillis();
        counter.test1();
        long end = System.currentTimeMillis();
        System.out.println("test1" + " consume: " + (end - start));
    }

    @Override
    public void test2() {
        long start = System.currentTimeMillis();
        counter.test2();
        long end = System.currentTimeMillis();
        System.out.println("test2" + " consume: " + (end - start));
    }
}

这就是 静态代理 的基本使用。

看起来很简单,但是应用场景也很明显。在需要对接口方法做统一处理的场景下,典型的如 Retrofit,静态代理就是个灾难,你得为每一个接口方法都提供实现。

让静态代理动起来

有没有办法把这个过程自动化呢?这就是 动态代理 的功能:自动生成代理类。

自动生成 Class 文件的方案很多,生写硬怼,Javassist,ASM 都可以。那么,是不是照着静态代理类的结构,在运行时自动生成就可以了呢?

这样设计显然是不合理的,为每个接口方法动态生成实现逻辑,那每个方法单独的处理逻辑从哪来呢?

合理的做法应该把所有接口方法桥接收拢,统一代理给一个接口方法 invoke(),将 方法名称和参数 作为 invoke() 方法的参数,这样动态代理的使用者就可以针对 methodName 进行对应处理。

现在你可以猜想一下动态生成的代理类的具体结构:

  1. 代理类不依赖具体的实现类,所以它也应该实现被代理的接口

  2. 代理类需要把所有接口方法的实现桥接给同一个接口,假设叫做 InvocationHandler.invoke()

    用伪代码表示:

    public class DynamicProxy implements ICount {
      
      private Method method1 = Class.forName("proxy.ICount").getMethod("test1");
      private Method method2 = Class.forName("proxy.ICount").getMethod("test2");
    
      private InvocationHandler handler;
      
      public DynamicProxy(InvocationHandler handler) {
        this.handler = handler;
      }
      
      @Override
      public void test1() {
        handler.invoke(this, method1, (Object[])null)
      }
      
      @Override
      public void test2() {
        handler.invoke(this, method2, (Object[])null)
      }
      
    }
    

    除了一些代码细节,这其实已经很接近真正生成的动态代理类了。

    我们再来看 JDK 中动态代理的使用方法:

    Counter counter = new Counter();
    ICount proxyCount = (ICount) Proxy.newProxyInstance(counter.getClass().getClassLoader(), counter.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    long start = System.currentTimeMillis();
                    Object result = method.invoke(counter, args);
                    long end = System.currentTimeMillis();
                    System.out.println(method.getName() + " consume: " + (end - start));
                    return result;
                }
            });
    proxyCount.test1();
    proxyCount.test2();
    

    核心点在于通过 Proxy.newProxyInstance() 动态生成代理类。它有三个参数:

    • ClassLoader loader : 类加载器
    • Class<?>[] interfaces :要代理的接口
    • InvocationHandler h : 所有接口方法会被桥接到 InvocationHandler 的 invoke() 方法。

    invoke() 方法有三个参数。proxy 是代理类,method 是代理的方法,args 是代理方法的参数。既满足了对代理方法的统一处理,也可以针对 method 做单独处理。

    完全符合我们之前的伪代码。

    通过下面的 JDK 的参数配置,可以在当前目录直接生成动态代理类的 class 文件。

    System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
    

    你可以在 这里 查看生成的 com.sun.proxy.$Proxy0.class 文件。

    源码解析

    动态代理的原理很简单,运行时动态生成并加载代理类 。我们跟进源码,再加深一下印象。

    PS:以下代码基于 OpenJdk 15 。

    Proxy.newProxyInstance() 开始。

        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h) {
            Objects.requireNonNull(h);
    
            final Class<?> caller = System.getSecurityManager() == null
                                        ? null
                                        : Reflection.getCallerClass();
    
            // 1. 获取代理类的 Constructor
            Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
            // 2. 创建代理类实例
            return newProxyInstance(caller, cons, h);
        }
    

    先看代码 2 处的 newProxyInstance() :

        private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
                                               Constructor<?> cons,
                                               InvocationHandler h) {
            try {
                if (caller != null) {
                    // 1. 权限检查
                    checkNewProxyPermission(caller, cons.getDeclaringClass());
                }
                // 2. 创建代理类实例
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException | InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                ......
            }
        }
    

    直接调用 Constructor.newInstance() 方法创建代理类实例,注意构造器参数是传入的 InvocationHandler 对象。

    重点在于如何获取代理类的构造器?再回到代码 1 处的 getProxyConstructor()

        private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                          ClassLoader loader,
                                                          Class<?>... interfaces)
        {
            // 单接口的优化处理
            // 为了方便,我们直接看这里
            if (interfaces.length == 1) {
                Class<?> intf = interfaces[0];
                if (caller != null) {
                    checkProxyAccess(caller, loader, intf);
                }
                // 从 proxyCache 中获取,或者通过 ProxyBuilder 新建
                return proxyCache.sub(intf).computeIfAbsent(
                    loader,
                    (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
                );
            } else {
                ......
            }
        }
    

    这里提供了 Constructor 的缓存类 proxyCache 避免重复生成。如果缓存中没有,就通过 ProxyBuilder 获取构造器,传入的参数是 Classloader 和 Interface 。

    直接看 ProxyBuilder.build() 方法。

            Constructor<?> build() {
                // 1. 获取代理类的 Class 对象
                Class<?> proxyClass = defineProxyClass(module, interfaces);
                final Constructor<?> cons;
                try {
                    // 2. 根据代理类的 Class 对象获取构造器
                    cons = proxyClass.getConstructor(constructorParams);
                } catch (NoSuchMethodException e) {
                    throw new InternalError(e.toString(), e);
                }
                ......
                return cons;
            }
    

    核心在于获取代理类 Class 对象的 defineProxyClass() 方法。

            private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
                String proxyPkg = null;     
                // 代理类是 public final 的
                int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
                ......
                if (proxyPkg == null) {
                    // PROXY_PACKAGE_PREFIX 值为 com.sun.proxy
                    proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
                                           : PROXY_PACKAGE_PREFIX;
                } else if (proxyPkg.isEmpty() && m.isNamed()) {
                    throw new IllegalArgumentException(
                            "Unnamed package cannot be added to " + m);
                }
                ......
                /*
                 * proxyClassNamePrefix 值为 $Proxy
                 * 生成的代理类名 com.sun.proxy.$Proxy0
                 * 结尾数字从 0 开始递增
                 */
                long num = nextUniqueNumber.getAndIncrement();
                String proxyName = proxyPkg.isEmpty()
                                        ? proxyClassNamePrefix + num
                                        : proxyPkg + "." + proxyClassNamePrefix + num;
                ......
                /*
                 * 生成代理类字节码
                 */
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                        proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
                try {
                    Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
                                                     0, proxyClassFile.length,
                                                     loader, null);
                    reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
                    return pc;
                } catch (ClassFormatError e) {
                    throw new IllegalArgumentException(e.toString());
                }
            }
    

    核心在于生成代理类字节码的 ProxyGenerator.generateProxyClass() 方法,有三个参数:

    • name : 代理类名称,$Proxy0
    • interfaces : 代理接口,数组
    • accessFlags :Modifier.PUBLIC | Modifier.FINAL
    static byte[] generateProxyClass(ClassLoader loader, final String name, List<Class<?>>       interfaces, int accessFlags) {
            ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags);
            // 生成字节码文件
            final byte[] classFile = gen.generateClassFile();
            if (saveGeneratedFiles) {
                /*
                 * 可以通过配置 jdk.proxy.ProxyGenerator.saveGeneratedFiles 参数
                 * 将字节码文件输出到本地文件
                 */
            }
    
            return classFile;
        }
    

    再跟进 ProxyGenerator.generateClassFile()

        private byte[] generateClassFile() {
            this.visit(58, this.accessFlags, dotToSlash(this.className), (String)null, "java/lang/reflect/Proxy", typeNames(this.interfaces));
            // 添加 hashCode 方法
            this.addProxyMethod(hashCodeMethod);
            // 添加 equals 方法
            this.addProxyMethod(equalsMethod);
            // 添加 toString 方法
            this.addProxyMethod(toStringMethod);
            Iterator var1 = this.interfaces.iterator();
    
            while(var1.hasNext()) {
                Class<?> intf = (Class)var1.next();
                Method[] var3 = intf.getMethods();
                int var4 = var3.length;
    
                for(int var5 = 0; var5 < var4; ++var5) {
                    Method m = var3[var5];
                    if (!Modifier.isStatic(m.getModifiers())) {
                        // 添加代理接口方法
                        this.addProxyMethod(m, intf);
                    }
                }
            }
    
            var1 = this.proxyMethods.values().iterator();
    
            List sigmethods;
            while(var1.hasNext()) {
                sigmethods = (List)var1.next();
                checkReturnTypes(sigmethods);
            }
            // 生成构造器
            this.generateConstructor();
            var1 = this.proxyMethods.values().iterator();
    
            while(var1.hasNext()) {
                sigmethods = (List)var1.next();
                Iterator var8 = sigmethods.iterator();
    
                while(var8.hasNext()) {
                    ProxyMethod pm = (ProxyMethod)var8.next();
                    this.visitField(10, pm.methodFieldName, "Ljava/lang/reflect/Method;", (String)null, (Object)null);
                    pm.generateMethod(this, this.className);
                }
            }
            // 生成静态代码块
            this.generateStaticInitializer();
            return this.toByteArray();
        }
    

    ProxyGenerator 继承自 ClassWriter ,通过 ASM 生成了字节码。这块就不细看了,具体 API 我也不是很清楚。挑一个生成构造器的 generateConstructor() 方法看一下:

        private void generateConstructor() {
            MethodVisitor ctor = this.visitMethod(1, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", (String)null, (String[])null);
            ctor.visitParameter((String)null, 0);
            ctor.visitCode();
            ctor.visitVarInsn(25, 0);
            ctor.visitVarInsn(25, 1);
            ctor.visitMethodInsn(183, "java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false);
            ctor.visitInsn(177);
            ctor.visitMaxs(-1, -1);
            ctor.visitEnd();
        }
    

    可以看到确实是将 InvocationHandler 作为了构造器参数,但并不是直接给代理类生成的,而是 Proxy 类。代理类是继承自 Proxy 类,并引用父类的构造器。

    生成代理类字节码文件之后,通过 UnSafe.defineClass() 注册到 VM 。

        public Class<?> defineClass(String name, byte[] b, int off, int len,
                                    ClassLoader loader,
                                    ProtectionDomain protectionDomain) {
            if (b == null) {
                throw new NullPointerException();
            }
            if (len < 0) {
                throw new ArrayIndexOutOfBoundsException();
            }
    
            return defineClass0(name, b, off, len, loader, protectionDomain);
        }
    
        public native Class<?> defineClass0(String name, byte[] b, int off, int len,
                                            ClassLoader loader,
                                            ProtectionDomain protectionDomain);
    

    提问环节

    到这,动态代理的流程就跟完了。

    经典提问环节:动态代理只能代理接口吗?如果是,为什么?

    在评论区,留下你的答案吧!

转载自:https://juejin.cn/post/7134712273871831048
评论
请登录