• 动态代理:JDK原生动态代理(Java Proxy)和CGLIB动态代理原理+附静态态代理


    本文只是对原文的梳理总结,以及自行理解。自己总结的比较简单,而且不深入,不如直接看原文。不过自己梳理一遍更有助于理解。
    详细可参考原文:http://www.cnblogs.com/CarpenterLee/p/8241042.html原文很强大,多看几遍,深入理解。

    原文中参考:https://www.jianshu.com/p/e2917b0b9614 (比原文的代码更详细,更有助于理解)


    自行总结:

    1. 两种代理方式,都是提供一个方法调用的中转站用于实现代理:CGLIG中MethodInterceptor和跟JDK代理中的InvocationHandler;
    2. 都是通过特定类的特定方法得到代理对象:
      1. Proxy.newProxyInstance() 方法入参中包含:类加载器(被代理对象的)、代理需要实现的接口,可以有多个(被代理对象的)、方法调用的实际处理者(即实际代理对象)
      2. Enhancer.create() 方法入参中包含:Superclass (被代理的类) 和 Callback (代理类)
    3. 调用代理对象的对应方法,会自动被代理到 invoke() 方法和 intercept() 方法。可在方法中加入代理的逻辑
    4. 最终还需要调用实际被代理对象的方法:Method. invoke() 和 MethodProxy.invokeSuper() 。

    一、静态代理的实现

    1. 声明接口;
    2. 编码接口实现类,需继承对应接口,实现对应方法;
    3. 编码代理类,需继承对应接口,实现对应方法。代理类内部方法实际调用具体实现类的方法,同时加入额外逻辑。

    具体实现可参考原文。

    二、JDK原生动态代理:基于接口

    1、动态代理:实际就是JDK定义类和方法(Proxy.newProxyInstance()),我们只需传入相关入参,JDK会自行为我们创建代理类,以实现代理功能。

    2、使用实现:

    // Java Proxy
    // 1. 首先实现一个InvocationHandler,方法调用会被转发到该类的invoke()方法。
    class LogInvocationHandler implements InvocationHandler{
        ...
        private Hello hello;
        public LogInvocationHandler(Hello hello) {
            this.hello = hello;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if("sayHello".equals(method.getName())) {
                logger.info("You said: " + Arrays.toString(args));
            }
            return method.invoke(hello, args);
        }
    }
    // 2. 然后在需要使用Hello的时候,通过JDK动态代理获取Hello的代理对象。
    Hello hello = (Hello)Proxy.newProxyInstance(
        getClass().getClassLoader(), // 1. 类加载器(被代理对象的)
        new Class<?>[] {Hello.class}, // 2. 代理需要实现的接口,可以有多个(被代理对象的)
        new LogInvocationHandler(new HelloImp()));// 3. 方法调用的实际处理者(即实际代理对象)
    System.out.println(hello.sayHello("I love you!"));

    3、具体使用总结:

    1. 首先实现一个InvocationHandler,方法调用会被转发到该类的invoke()方法。invoke() 方法就是实际的代理方法。
    2. 需要使用Hello时,通过JDK动态代理获取Hello的代理对象。
    3. 调用代理对象的接口声明方法,会被自动转发到invoke()方法上。

    4、上述代码理解:

    关键是:Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)方法
    该方法会根据指定的参数动态创建代理对象。三个参数的意义如下:
    
    loader,指定代理对象的类加载器;
    interfaces,代理对象需要实现的接口,可以同时指定多个接口;
    handler,方法调用的实际处理者,代理对象的方法调用都会转发到这里(*注意1)。
    
    newProxyInstance()会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给InvocationHandler.invoke()方法。
    理解上述代码需要对Java反射机制有一定了解。动态代理神奇的地方就是:
    -代理对象是在程序运行时产生的,而不是编译期;
    -对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;之后我们通过某种方式执行真正的方法体,示例中通过反射调用了Hello对象的相应方法,还可以通过RPC调用远程方法。

    5、其他理解:参考JDK官方文档即可

    1. InvocationHandler(接口)和其invoke()方法理解:
    接口:
    java.lang.reflect.InvocationHandler
    方法:在代理实例上处理方法调用并返回结果。
    Object  invoke(Object proxy, Method method, Object[] args) 
    参数:
    proxy - 在其上调用方法的代理实例(即代理对象)
    method - 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。(即代理方法)
    args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
    返回:
    从代理实例的方法调用返回的值。如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;否则,它一定是可分配到声明返回类型的类型。如果此方法返回的值为 null 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出 NullPointerException。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出 ClassCastException。
    1. Proxy(类)的理解:
    类:java.lang.reflect.Proxy
    
    方法:返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
    static Object  newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    参数:
    loader - 定义代理类的类加载器
    interfaces - 代理类要实现的接口列表
    h - 指派方法调用的调用处理程序
    返回:
    一个带有代理类的指定调用处理程序(即入参:h )的代理实例,它由指定的类加载器(即入参:loader)定义,并实现指定的接口(即入参:interfaces)
    1. 对于不同invoke()方法的理解:
    java.lang.reflect.Method.invoke(Object, Object...)
    对带有指定参数的指定对象调用由此 Method 对象表示的底层方法 
    //即该方法所属对象的该方法调用。
    
    java.lang.reflect.InvocationHandler.invoke(Object, Method, Object[])

    三、CGLIB动态代理

    大致原理和JDK动态代理一致,方便使用。

    优点在于:可以代理没有实现任何接口的类;

    1、使用实现:

    //需被代理的类
    public class HelloConcrete {
        public String sayHello(String str) {
            return "HelloConcrete: " + str;
        }
    }
    
    // CGLIB动态代理
    // 1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
    class MyMethodInterceptor implements MethodInterceptor{
      ...
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            logger.info("You said: " + Arrays.toString(args));
            return proxy.invokeSuper(obj, args);
        }
    }
    // 2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象。
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(HelloConcrete.class); //被代理的类
    enhancer.setCallback(new MyMethodInterceptor());//代理类
    
    HelloConcrete hello = (HelloConcrete)enhancer.create(); //代理对象
    System.out.println(hello.sayHello("I love you!"));

    2、具体使用总结:

    1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
    2. 然后在需要使用HelloConcrete的时候,设置参数,同时通过CGLIB动态代理获取代理对象。
    3. 调用代理对象的代理方法,然后会自行实现其调用

    3、上述代码理解:

    1、通过CGLIB的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,
    2、最终通过调用create()方法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法,
    3、在intercept()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;
    4、通过调用MethodProxy.invokeSuper()方法,我们将调用转发给原始对象,具体到本例,就是HelloConcrete的具体方法。
    
    CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很类似,都是方法调用的中转站。

    三、结束。

  • 相关阅读:
    Java Web系统经常使用的第三方接口
    ExtJS笔记--applyTo和renderTo的差别
    ORACLE触发器具体解释
    java多线程样例
    RapeLay(电车之狼R)的结局介绍 (隐藏结局攻略)
    排序——选择排序
    常见hash算法的原理
    jdk和jre是什么?都有什么用?(转帖)
    Ubuntu下deb包的安装方法
    參加《全流程全要素的研发项目管理》培训记录与心得
  • 原文地址:https://www.cnblogs.com/buwuliao/p/9113012.html
Copyright © 2020-2023  润新知