署理形式能够说是使用最为广泛的规划形式之一,同时也是其他一些规划形式的根底或组成部分。

在上篇文章 浅显易懂 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);
    

    发问环节

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

    经典发问环节:动态署理只能署理接口吗?假如是,为什么?

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