zoukankan      html  css  js  c++  java
  • Java 动态代理的简单使用和理解

    前言

    在 Java 中,动态代理是一个很常用的功能,虽然说一般不需要自己直接去用,但是了解它们是怎么回事还是很有必要的。

    这篇博客的主要内容便是 JDK 动态代理和 CGLIB 动态代理的简单使用和理解。

    JDK 动态代理

    JDK 动态代理依赖于 接口 来确定它需要代理的方法,使用时可以分为以下几个角色:

    • TargetInterfaces - 需要代理的目标接口(们),JDK 动态代理将会为这些接口的 方法调用 创建代理
    • TargetObject - 实现了目标接口的对象
    • InvocationHandler - 方法调用 处理器,JDK 动态代理在内部通过 InvocationHandler 对象来处理目标方法的调用
    • java.lang.reflect.Proxy - 组装 InvocationHandlerTargetObject, 创建代理对象,创建出来的代理对象是它的子类的实例

    TargetInterfacesTargetObject 相对来说很容易理解,就是一些接口和实现了这些接口的对象,比如说:

    interface TargetInterfaceA {
      void targetMethodA();
    }
    
    interface TargetInterfaceB {
      void targetMethodB();
    }
    
    class TargetClass implements TargetInterfaceA, TargetInterfaceB {
      @Override
      public void targetMethodA() {
        System.out.println("Target method A...");
      }
    
      @Override
      public void targetMethodB() {
        System.out.println("Target method B...");
      }
    }
    

    上面的例子中,目标接口为 [TargetInterfaceA, TargetInterfaceB], 而目标对象就是 TargetClass实例.

    现在,我们想要拦截 TargetClass 实现的接口的方法的调用,我们需要先通过 InvocationHandler 来定义代理逻辑:

    class SimpleInvocationHandler implements InvocationHandler {
      private Object target;
    
      public SimpleInvocationHandler(Object target) {
        this.target = target;
      }
    
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(String.format("Before invocation method %s", method.getName()));
        Object result = method.invoke(target, args);
        System.out.println(String.format("After invocation method %s", method.getName()));
        return result;
      }
    }
    

    InvocationHandler 这个接口只定义了一个方法 invoke, 该方法的参数为:

    • proxy - 代理对象实例,注意,不是 TargetObject, 是 Proxy 子类的实例,因此,我们需要在 InvocationHandler 实例内部持有 TargetObject
    • method - 要调用的方法
    • args - 方法调用参数

    有了 InvocationHandlerTargetClass 之后,我们就可以创建 TargetObject 并通过 Proxy 组装创建代理对象了,主要通过 newProxyInstance 方法完成:

    TargetClass targetObject = new TargetClass();
    Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), new SimpleInvocationHandler(targetObject););
    

    方法 Proxy.newProxyInstance 的参数为:

    • ClassLoader - 一个 ClassLoader, 简单的话直接用 targetObjectClassLoader 就可以了
    • Class<?>[] - 要代理的接口数组,同样,直接获取 targetObject 实现的所有接口
    • InvocationHandler - 定义了方法调用处理逻辑的 InvocationHandler

    PS: 可以看到,创建代理对象的时候需要我们先创建 TargetObject 才行,而且还需要手动将 TargetObject 传递给 InvocationHandler, 很麻烦……

    完整代码和测试:

    interface TargetInterfaceA {
        void targetMethodA();
    }
    
    interface TargetInterfaceB {
        void targetMethodB();
    }
    
    class TargetClass implements TargetInterfaceA, TargetInterfaceB {
        @Override
        public void targetMethodA() {
            System.out.println("Target method A...");
        }
    
        @Override
        public void targetMethodB() {
            System.out.println("Target method B...");
        }
    }
    
    class SimpleInvocationHandler implements InvocationHandler {
        private Object target;
    
        public SimpleInvocationHandler(Object target) {
            this.target = target;
        }
    
        public static Object bind(Object targetObject) {
            SimpleInvocationHandler handler = new SimpleInvocationHandler(targetObject);
            return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), handler);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println(String.format("Before invocation method %s", method.getName()));
            Object result = method.invoke(target, args);
            System.out.println(String.format("After invocation method %s", method.getName()));
            return result;
        }
    }
    
    public class ProxyTest {
      public static void main(String[] args) {
        Object proxy = SimpleInvocationHandler.bind(new TargetClass());
        ((TargetInterfaceA) proxy).targetMethodA();
        ((TargetInterfaceB) proxy).targetMethodB();
      }
    }
    

    输出为:

    Before invocation method targetMethodA
    Target method A...
    After invocation method targetMethodA
    Before invocation method targetMethodB
    Target method B...
    After invocation method targetMethodB
    

    代理类

    在运行代码时可以将下面这行代码放在最前面查看 Proxy 动态生成的代理类是怎样的:

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

    前面的代码生成的代理类为:

    final class $Proxy0 extends Proxy implements TargetInterfaceA, TargetInterfaceB {
      private static Method m0;
      private static Method m1;
      private static Method m2;
      private static Method m4;
      private static Method m3;
    
      static {
        try {
          m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
          m2 = Class.forName("java.lang.Object").getMethod("toString");
    
          // 目标接口中定义的方法
          m4 = Class.forName("classload.TargetInterfaceB").getMethod("targetMethodB");
          m3 = Class.forName("classload.TargetInterfaceA").getMethod("targetMethodA");
    
          m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
          throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
          throw new NoClassDefFoundError(var3.getMessage());
        }
      }
    
      public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
      }
    
      // 通过 InvocationHandler 来调用目标方法
      public final void targetMethodA() throws  {
        try {
          super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
          throw var2;
        } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
        }
      }
    
      // 通过 InvocationHandler 来调用目标方法
      public final void targetMethodB() throws  {
        try {
          super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
          throw var2;
        } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
        }
      }
    }
    

    通过阅读代理类的代码我们可以发现:

    • 代理类继承了 Proxy 并实现了目标接口
    • 代理类在静态初始化块通过反射获取了目标接口的方法
    • 代理类实现的接口方法会通过 InvocationHandler 来调用目标方法
    • InvocationHandler 传递的第一个参数为代理对象,不是 TargetObject1

    另外,代理类还获取了 Object 的 hashCode、equals 和 toString 方法,它们的调用逻辑都是一样的,就是直接调用 InvocationHandler 对象对应的方法,比如:

    public final int hashCode() throws  {
      try {
        return (Integer)super.h.invoke(this, m0, (Object[])null);
      } catch (RuntimeException | Error var2) {
        throw var2;
      } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
      }
    }
    

    因此,我们也是可以代理目标对象的这些方法的。

    CGLIB 动态代理

    CGLIB 动态代理和 JDK 动态代理类似,只不过 CGLIB 动态代理是基于类的,不需要实现接口,简单使用的话只需要定义一个 MethodInterceptor 就可以了, 相当于 JDK 动态代理中的 InvocationHandler.

    class SimpleMethodInterceptor implements MethodInterceptor {
      @Override
      public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println(String.format("Before invocation method %s", method.getName()));
        Object result = proxy.invokeSuper(obj, args);
        System.out.println(String.format("After invocation method %s", method.getName()));
        return result;
      }
    }
    

    有了 MethodInterceptor 后我们就可以创建代理对象了:

    class ProxyTest {
      public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        // 设置需要被代理的类
        enhancer.setSuperclass(TargetClass.class);
        // 设置 MethodInterceptor
        enhancer.setCallback(new SimpleMethodInterceptor());
        // 创建代理对象
        TargetClass proxyObject = (TargetClass) enhancer.create();
        // 调用方法
        proxyObject.targetMethodA();
        proxyObject.targetMethodB();
      }
    }
    
    
    class TargetClass {
      public void targetMethodA() {
        System.out.println("Target method A...");
      }
    
      public void targetMethodB() {
        System.out.println("Target method B...");
      }
    }
    

    执行输出为:

    Before invocation method targetMethodA
    Target method A...
    After invocation method targetMethodA
    Before invocation method targetMethodB
    Target method B...
    After invocation method targetMethodB
    

    代理类

    对于 CGLIB 来说可以设置 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY 属性的值来保存生成的代理类2

    public class TargetClass$$EnhancerByCGLIB$$eb42b691 extends TargetClass implements Factory {
      private MethodInterceptor CGLIB$CALLBACK_0;
    
      static void CGLIB$STATICHOOK1() {
        // 要代理的目标方法
        var10000 = ReflectUtils.findMethods(new String[]{"targetMethodA", "()V", "targetMethodB", "()V"}, (var1 = Class.forName("TargetClass")).getDeclaredMethods());
        CGLIB$targetMethodA$0$Method = var10000[0];
        CGLIB$targetMethodA$0$Proxy = MethodProxy.create(var1, var0, "()V", "targetMethodA", "CGLIB$targetMethodA$0");
        CGLIB$targetMethodB$1$Method = var10000[1];
        CGLIB$targetMethodB$1$Proxy = MethodProxy.create(var1, var0, "()V", "targetMethodB", "CGLIB$targetMethodB$1");
      }
    
      // 目标方法的简单代理
      final void CGLIB$targetMethodA$0() {
        super.targetMethodA();
      }
    
      public final void targetMethodA() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
          CGLIB$BIND_CALLBACKS(this);
          var10000 = this.CGLIB$CALLBACK_0;
        }
    
        // 当 MethodInterceptor 不为空时通过 MethodInterceptor 调用目标方法
        if (var10000 != null) {
          var10000.intercept(this, CGLIB$targetMethodA$0$Method, CGLIB$emptyArgs, CGLIB$targetMethodA$0$Proxy);
        } else {
          super.targetMethodA();
        }
      }
    
      // 目标方法的简单代理
      final void CGLIB$targetMethodB$1() {
        super.targetMethodB();
      }
    
      public final void targetMethodB() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
          CGLIB$BIND_CALLBACKS(this);
          var10000 = this.CGLIB$CALLBACK_0;
        }
    
        // 当 MethodInterceptor 不为空时通过 MethodInterceptor 调用目标方法
        if (var10000 != null) {
          var10000.intercept(this, CGLIB$targetMethodB$1$Method, CGLIB$emptyArgs, CGLIB$targetMethodB$1$Proxy);
        } else {
          super.targetMethodB();
        }
      }
    
      final int CGLIB$hashCode$5() {
        return super.hashCode();
      }
    
      // 对 Object 方法的代理
      public final int hashCode() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
          CGLIB$BIND_CALLBACKS(this);
          var10000 = this.CGLIB$CALLBACK_0;
        }
    
        if (var10000 != null) {
          Object var1 = var10000.intercept(this, CGLIB$hashCode$5$Method, CGLIB$emptyArgs, CGLIB$hashCode$5$Proxy);
          return var1 == null ? 0 : ((Number)var1).intValue();
        } else {
          return super.hashCode();
        }
      }
    
      // ...
    }
    

    可以看到

    • CGLIB 每个方法设置了两个代理,一个直接调用父类方法,另一个判断是否存在 MethodInterceptor 来进行调用
    • 代理类继承了 TargetClass, 和 JDK 动态代理中继承 Proxy 的方式不一样

    当我们设置了 MethodInterceptor 以后,CGLIB 便可以通过 MethodInterceptor 来调用目标方法,另外,调用 MethodInterceptor.intercept 方法时传递的第一个参数为代理类实例, 因此,需要执行被代理的方法时,应该通过 MethodProxy.invokeSuper 来完成,如果使用 Method.invoke 的话就会导致无限递归调用。

    Spring @Configuration

    在使用 Spring 的时候我们可以通过如下方式定义 Bean:

    @Configuration
    @ComponentScan(basePackageClasses = Company.class)
    public class Config {
      @Bean
      public Address getAddress() {
        return new Address("High Street", 1000);
      }
    
      @Bean
      public Person getPerson() {
        return new Person(getAddress());
      }
    }
    

    当初对于这种方式的一种困惑就是,Spring 是怎么拦截对 getAddress 方法的调用的,因为在我的印象中 JDK 动态代理做不到这样的事情,现在才发现,Spring 会通过 CGLIB 为 Config 创建代理对象,拦截对 getAddress 方法的调用,保证 Bean 的单例性。

    因为在 Java 中会根据先对象的 实际类型 查找方法,找不到才到 父类 中进行查找,而恰好的是,CGLIB 创建的代理对象是覆盖了父类的方法的,这样一来,在代理类中通过 MethodInterceptor 拦截方法的调用就可以避免重复创建 Bean 了。

    这在 Spring 中对应的 MethodInterceptorConfigurationClassEnhancer.BeanMethodInterceptor.

    小结

    这里汇总一下 JDK 动态代理和 CGLIB 动态代理的代理方式:

    • JDK 动态代理通过创建继承了 Proxy 并实现了 TargetInterfaces 的代理类来完成代理,调用 TargetInterfaces 的方法时,代理类会将方法调用转交给 InvocationHandler 完成
    • CGLIB 动态代理通过创建继承了 TargetClass 的代理类来完成代理,调用 TargetClass 的方法时,如果 MethodInterceptor 不为空,那么就会将方法调用转交给 MethodInterceptor 完成

    可以看到,两种实现动态代理的方式还是很接近的,只不过一个是通过接口,一个是通过子类。

    结语

    最开始接触动态代理这个概念是在看《Java 核心技术卷》这本书的时候,当时刚开始学 Java 没多久,看到这个东西后的想法就是,这么不方便的东西, 谁没事会去用啊 ‍→_→

    结果,它的使用很广泛 ‍( ´`)

    类似的还有源码注解,不得不说,这些操作起来麻烦,但是功能又强大的特性,总会有人完成花样来‍╮( ̄▽ ̄)╭

    Footnotes

    1 最开始看到 InvocationHandler 这个接口的时候总以为它的第一个参数为 TargetObject

    2 为了方便阅读省略了很多其他不必要的内容

  • 相关阅读:
    CentOS(RedHat) 6.2 Samba share权限拒绝访问
    Android NDK调试C++源码(转)
    linux du命令: 显示文件、目录大小
    网络游戏的同步
    游戏开发辅助库
    Unity3D 200个插件免费分享
    C#UDP同步实例
    C#UDP(接收和发送源码)源码完整
    C#完整的通信代码(点对点,点对多,同步,异步,UDP,TCP)
    内置函数及匿名函数
  • 原文地址:https://www.cnblogs.com/rgbit/p/12312038.html
Copyright © 2011-2022 走看看