> 文章列表 > 【Java学习笔记】 动态代理

【Java学习笔记】 动态代理

【Java学习笔记】 动态代理

文章目录

    • 动态代理
      • 1、什么是动态代理?
        • 动态代理常见的实现技术包括以下三种
      • 2、Java内置的动态代理
        • 1)如何使用Proxy
        • 2)调用处理器InvocationHandler
          • 思考:为什么要强制实现InvocationHandler接口呢?
          • 思考2:invoke方法什么时候被调用?(如何调用?)
        • 3)invoke方法的使用
          • 那么,如何使用Method来调用目标方法呢?
        • 4)invoke方法的返回值
        • 5)进一步封装一个自定义的工具方法
      • 3、CGLIB 动态代理
        • 1)什么是CGLIB(和JDK动态代理的区别
        • 2)用法/写法
        • 3)回调如何设置?
        • 4)测试并观察代理对象的运行结果
          • 测试代码如下:

动态代理

1、什么是动态代理?

前面已经提到了,动态代理就是【在内存】中动态生成【字节码代理类】的技术。(虽然不需要开发者书写,但是在内存层面依然存在该代理对象】

优点

  • 减少了代理类的数量
  • 并且解决了代码复用的问题。

动态代理常见的实现技术包括以下三种

  • JDK内置的动态代理技术 :只能代理接口
    • 位置:java.lang.reflect.Porxy ,是一个注解
  • CGLIB(Code Generation Library)动态代理技术,一个开源项目,生成类库,可以适用于接口和类
    • 但是CGLIB的低层是通过【继承】实现的(虽然是继承,但是由于是在内存动态生成字节码类,所以并不会增加耦合度),所以性能比JDK动态代理好
    • CGLIB的低层还有一个字节码处理框架 【ASM】(可能阅读源码时会遇到)
  • Javassist动态代理技术:东京大学的千叶滋教授所创建的开源项目。为JBOOS实现“aop”框架
    • mybatis框架底层就是用的javassist创建接口的字节码对象

Spring的低层主要是靠JDK内置的动态代理和CGLIB实现

2、Java内置的动态代理

1)如何使用Proxy

还是模拟静态代理的场景。接下来展示一下如何使用。

// 用法
OrderService target = new OrderServiceImpl();
Object proxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler());

【Java学习笔记】 动态代理

  • newProxyInstance,翻译就是新建代理对象,本质上该方法做了两件事
    • 在内存中动态的生成了一个代理类的字节码class
    • "new"对象了,通过内存中生成代理类,实例化了该代理对象
  • newProxyInstance() 方法有三个参数,分别的作用进行分析
    • ClassLoader loader ——类加载器,将字节码class文件加载到内存当中。而且JDK要求,目标类的类加载器,必须和代理类的类加载器使用同一个
    • Class<>?[] interfaces ——代理类和目标类实现的共同接口
    • InvocationHandler h —— 翻译是调用处理器

2)调用处理器InvocationHandler

这个参数的含义,在理解之前可以做一个简单的推测,我们在使用代理对象时,增强功能的代码应该写在哪里?(首先JDK肯定是不知道开发者要写什么代码的) 目前的三个参数已经用掉了两个,所以,不难推测调用处理器的作用。结合老杜的笔记,如下:

调用处理器的作用:写增强代码。同时InvocationHandler的接口,那我们就需要实现并重写该接口,再作为参数传入Proxy

重写后就可以发现,该接口需要重写一个方法——invoke()

public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
思考:为什么要强制实现InvocationHandler接口呢?
  • 因为一个类实现接口必须实现接口中的方法
  • 以下的方法必须是invoke(),因为JDK在低层调用invoke的方法已经写好了
    • 也就是说,invoke方法并不是开发者调用,而是为了给JDK调用
思考2:invoke方法什么时候被调用?(如何调用?)
  1. 首先尝试重写invoke方法

    【Java学习笔记】 动态代理

  2. 调用target(目标对象)—— 还是调用原对象

    【Java学习笔记】 动态代理

  3. 尝试将newProxyInstance的返回值进行转型,向下转型为OrderService,并尝试调用。代码如下

    OrderService target = new OrderServiceImpl();OrderService obj  = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler());
    obj.insert();
    obj.modify();
    
    • 发现打印出INVOKE了,但是,目标对象的真正的方法无了,没有实现代理方法

      【Java学习笔记】 动态代理

3)invoke方法的使用

invoke方法有如下三个参数

【Java学习笔记】 动态代理

  • 第一个参数:代理对象的引用
  • 第二个参数,目标对象上的目标方法
  • 第三个参数,目标方法上的实参

核心思路: invoke方法在执行过程中,使用method方法来调用目标对象的目标方法

那么,如何使用Method来调用目标方法呢?

Method是Java反射的一个类,具体用法需要参照一下API给出的解释。

【Java学习笔记】 动态代理


也就是说方法四要素:哪个对象、哪个方法、传什么参数、传什么值。

而我们这边需要调用目标对象的目标方法,还缺一个关键 —— 目标对象

那么这里就需要使用构造函数将目标对象传入到该方法内以供调用,经过分得出以下方法,如下。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class TimerInvocationHandler implements InvocationHandler {private Object target;public TimerInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(">>>BEFORE INVOKE ....");long begin = System.currentTimeMillis();Object retValue = method.invoke(target, args);long end = System.currentTimeMillis();System.out.println(">>>AFTER  INVOKE...COST:"+(end-begin));return null;}
}

客户端

public class Client {public static void main(String[] args) {OrderService target = new OrderServiceImpl();OrderService obj = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler(target));obj.insert();obj.modify();}
}

输出结果

【Java学习笔记】 动态代理

这里我们增强了普通的无返回值的方法,那么如何代理(增强)带返回值的方法呢?

4)invoke方法的返回值

对OrderService做如下修改,新增了一个带返回值的方法以便测试

【Java学习笔记】 动态代理

  • 尝试在上述代码中,不做修改,直接调用代理对象 —— 返回值为空

    【Java学习笔记】 动态代理

  • 那么在Invoke里 将 方法执行结果return。—— 即可得到预期的结果

    【Java学习笔记】 动态代理

注意:这个invoke 方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话:invoke 方法必须将目标对象的目标方法执行结果继续返回。

至此,已经完成了通过JDK内置的代理类,实现使用代理模式增强业务代码的目的了。但是代码其实其实还是有点多,可以尝试使用工具类进行一个封装优化。

5)进一步封装一个自定义的工具方法

这里老杜的封装其实不是很严谨,应该传参还需要设置成可以传入一个自定义的处理器,这样这个工具类才会更加易用。改进后如下

工具类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;public class ProxyUtil {/* 生成代理对象* @param target 目标对象* @param handler 调用处理器* @return 代理对象*/public static Object getProxyInstance(Object target , InvocationHandler handler){Object proxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);return proxyInstance;}
}

优化后客户端写法如下:

public class Client {public static void main(String[] args) {OrderService target = new OrderServiceImpl();TimerInvocationHandler timerInvocationHandler = new TimerInvocationHandler(target);OrderService obj = (OrderService) ProxyUtil.getProxyInstance(target, timerInvocationHandler);}
}

3、CGLIB 动态代理

老杜提到,这里的CGLIB写法并不是重点,需要了解一个大概的写法,并且明白,是使用继承的特性实现的动态代理。所以,CGLIB会更加的泛用。

1)什么是CGLIB(和JDK动态代理的区别

  • CGLIB既可以代理接口,又可以代理类
  • 低层是采用继承的方式是实现的
  • 所以被代理的目标类,不能使用final修饰
  • 另外。CGLIB功能更强大,效率也更高

2)用法/写法

  1. 既然提到CGLIB可以继承普通类,那么我们就直接声明一个普通类作为目标对象
  2. 创建客户端类 Client
  3. 创建字节码增强对象 Enhancer,作为CGLIB库中的核心对象,就是依靠它来生存代理类的
  4. 设置父类(告诉CGLIB父类,即目标类是谁)
  5. 设置回调(相当于JDK代理中的调用处理器,invocationHandler)
  6. 创建代理对象:做两件事
    • 在内存中生成目标对象的子类,其实就是代理类的字节码
    • 创建代理对象(通过代理类

3)回调如何设置?

实现的思路和JDK动态地理类似,都是实现一个接口

  • InvocationHandler (方法四要素)
  • MethodInterceptor (方法四要素)

不同之处在于,target目标对象不需要再手动创建构造函数传入了

如果运行报错 ,是因为JDK版本太高了,需要加参数

--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED

4)测试并观察代理对象的运行结果

【Java学习笔记】 动态代理

  • 结果是成功的,实现了业务增强的效果。

  • 老杜这里提到,要注意这个打印出来的对象地址是比较特殊的,可以用于以后调试的时候查看

    【Java学习笔记】 动态代理

测试代码如下:

业务逻辑:

package com.zhc.cglib.proxy.service;public class OrderServiceCGlib {public void modify() {try {Thread.sleep(1500L);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("FINISH MODIFY");}public String getName() {System.out.println("GET NAME ...");return "ZHANGSAN";}
}

自定义方法拦截器:

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class TimerMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {long begin = System.currentTimeMillis();Object retValue = methodProxy.invokeSuper(target, objects);long end = System.currentTimeMillis();System.out.println("耗时:"+(end-begin));return retValue;}
}

客户端 :

package com.zhc.cglib.proxy.client;import com.zhc.cglib.proxy.service.OrderServiceCGlib;
import com.zhc.cglib.proxy.service.TimerMethodInterceptor;
import net.sf.cglib.proxy.Enhancer;public class CGlibClient {public static void main(String[] args) {/* 1、创建增强器* 2、设置父类* 3、设置回调* 4、获取代理对象*/Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OrderServiceCGlib.class);enhancer.setCallback(new TimerMethodInterceptor());OrderServiceCGlib orderServiceCGlib = (OrderServiceCGlib) enhancer.create();System.out.println(orderServiceCGlib.getName());orderServiceCGlib.modify();System.out.println(orderServiceCGlib);}
}