前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >动态代理是怎么 “动” 起来的?

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

作者头像
路遥TM
发布2022-12-10 16:06:10
2610
发布2022-12-10 16:06:10
举报
文章被收录于专栏:路遥的专栏路遥的专栏

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

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

静态代理

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

接口类 ICount 和实现类 Counter

代码语言:javascript
复制
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

代码语言:javascript
复制
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()

用伪代码表示:

代码语言:javascript
复制
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 中动态代理的使用方法:

代码语言:javascript
复制
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 文件。

代码语言:javascript
复制
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

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

源码解析

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

PS:以下代码基于 OpenJdk 15 。

Proxy.newProxyInstance() 开始。

代码语言:javascript
复制
    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() :

代码语言:javascript
复制
    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()

代码语言:javascript
复制
    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() 方法。

代码语言:javascript
复制
        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() 方法。

代码语言:javascript
复制
        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
代码语言:javascript
复制
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()

代码语言:javascript
复制
    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() 方法看一下:

代码语言:javascript
复制
    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 。

代码语言:javascript
复制
    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);

提问环节

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

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 路遥TM 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 静态代理
  • 让静态代理动起来
  • 源码解析
  • 提问环节
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档