SpringBoot_6:JDK动态代理和CGLIB动态代理

SpringBoot_6:JDK动态代理和CGLIB动态代理 目录一、认识两种代理模式二、静态代理三、JDK动态代理1、实现JDK动态代理2、控制处理器InvocationHandler3、调用时创建代理Proxy.newInstance()4、代理创建原理5、思考四、CGLIB动态代理1、实现CGLIB动态代理2、实现MethoInterceptor方法拦截器3、调用代理4、代理创建原理5、思考五、总结一、认识两种代理模式我们为什么需要代理假如你为一个业务编写了一套方法现在业务扩展了你需要在业务执行前或者执行后添加一些其他业务例如在原来的业务之后新增通知用户的业务在原来的业务之前进行额外校验还例如需要统计整个业务消耗的时间以便未来优化。那么你可能首先想到的是修改原来的业务增添我们目标逻辑。但是在面向对象的设计模式中其不符合“对扩展开放对修改关闭”的原则。也就是说我们不能改变原来的业务但是却仍要实现这些增强的逻辑。如何做呢考虑一种代理模型客户发起请求先被代理拦截下来然后代理负责添加增强逻辑同时代理调用原来的方法执行最初的业务逻辑。代理处理完后把结果再返回给客户。最初的代理模式只有静态代理然后出现JDK动态代理这是基于Java原生支持的代理模式。随后衍生出CGLIB动态代理模式两种动态代理的目的是一样的但是底层实现和原理完全不同。静态代理手写代理类在编译期就确定代理类缺点是类爆炸JDK动态代理要求目标对象必须实现接口代理类实现和目标类一样的接口CGLIB动态代理通过继承目标类来实现子类代理不需要目标类实现接口代理类就是一份字节码模板基于它我们可以实例化多个代理对象我们通过调用代理对象来实现我们的增强逻辑。二、静态代理静态代理是比动态代理出现更早的代理模式特点就是在编译器写代码就把代理类确定下来。例如在一个场景中我需要统计OrderService中这些方法执行耗时情况以便于后面优化时作为基本参考。这就是一个破坏原来业务代码的逻辑的典型如果我们给每个方法都添加了这些计时方法那将会破坏原来的业务逻辑。例如因此我们需要代理用静态代理的方式实现。先将OrderService的方法抽离到一个接口中。然后用OrderServiceImpl去实现OrderService,实现后的方法就是我们最初的业务逻辑增强逻辑需要放到代理中去package staticProxy; public class OrderServiceProxy implements OrderService{ // 持有原始业务逻辑接口的引用 private OrderService orderService; public OrderServiceProxy(OrderService orderService) { this.orderService orderService; } // 重写增强逻辑 Override public void createOrder(String userId) { Long startTime System.currentTimeMillis(); orderService.createOrder(userId); Long endTime System.currentTimeMillis(); System.out.println(createOrder耗时 (endTime - startTime) ms); } Override public void cancelOrder(String orderId) { Long startTime System.currentTimeMillis(); orderService.cancelOrder(orderId); Long endTime System.currentTimeMillis(); System.out.println(cancelOrder耗时 (endTime - startTime) ms); } Override public String queryOrder(String orderId) { Long startTime System.currentTimeMillis(); String queryOrder orderService.queryOrder(orderId); Long endTime System.currentTimeMillis(); System.out.println(queryOrder耗时 (endTime - startTime) ms); return queryOrder; } }这里我们看到静态代理就是需要手写代理类里面维护一个原始业务的引用构造方法中声明为接口类型调用时传入的是实现了该接口的对象。我们编写客户端测试public class Client { public static void main(String[] args) { // 创建原始业务的对象 OrderServiceImpl orderService new OrderServiceImpl(); // 将实现了OrderService接口的对象传入代理实例化代理类 OrderServiceProxy proxy new OrderServiceProxy(orderService); // 通过代理类调用增强方法 proxy.createOrder(1001); proxy.queryOrder(order_1001); proxy.cancelOrder(order_1001); } }这样我们的增强逻辑就实现了总结一下那我们思考一下在静态代理中如果我们需要使用代理类代理目标类那么就需要在编译期手写代理类。如果需要被代理的目标类多了那么代理了就会增多造成“类爆炸”。因此我们希望只有在用的时候才创建代理类不用的时候就不用创建代理类JDK动态代理和CGLIB动态代理就做了这件事情把动态代理类交给类加载器管理。在满足一定条件后可以被GC回收。三、JDK动态代理1、实现JDK动态代理JDK动态代理也是基于目标类实现接口实现的代理类同样实现接口。在JDK动态代理中还需要有一个“控制器”这个控制器具有很好的“通用性”控制器中放一些通用的增强逻辑。为展示通用性我们再创建一个用户服务以及接口。2、控制处理器InvocationHandler增强逻辑是在控制器中完成的当代理调用目标方法时会调用控制器里的invoke()方法。package dynamic.jdkproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class TimerInvocationHandler implements InvocationHandler { // 持有真实对象的引用类型为Object表示可以接收任意类型的对象 private Object target; // 实例化控制器同时实例化目标对象 public TimerInvocationHandler(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增强 Long startTime System.currentTimeMillis(); // 反射指定目标方法 Object result method.invoke(target,args); // 后置增强 Long endTime System.currentTimeMillis(); System.out.println(method.getName()耗时 (endTime - startTime) ms); // 返回调用结果(这里可以封装调用结构但是保留原来返回值返回) return result; } }我们为了实现JDK动态代理需要借用底层的一个接口InvocationHandler这里面的调用过程和invoke()方法我们稍后讲先看如何创建并使用代理。3、调用时创建代理Proxy.newInstance()我们说动态代理是在使用时才创建因此我们放在客户端。package dynamic.jdkproxy; import java.lang.reflect.Proxy; public class Client { public static void main(String[] args) { // 创建两个需要被代理的实例这里需要用接口类型的引用指向实现了接口的实例 OrderService orderService new OrderServiceImpl(); UserService userService new UserServiceImpl(); // 创建两个处理器虽然是两个实例但是复用一个类体现通用性 TimerInvocationHandler orderServiceHandler new TimerInvocationHandler(orderService); TimerInvocationHandler userServiceHandler new TimerInvocationHandler(userService); // 创建代理 OrderService orderServiceProxy (OrderService) Proxy.newProxyInstance( orderService.getClass().getClassLoader(), orderService.getClass().getInterfaces(), orderServiceHandler); UserService userServiceProxy (UserService) Proxy.newProxyInstance( userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), userServiceHandler); // 通过代理执行目标方法 orderServiceProxy.createOrder(10001); userServiceProxy.login(张三); } }4、代理创建原理在上面的代码中我们使用了一个关键方法Proxy.newProxyInstance()这个方法是JDK底层的一个方法有三个参数类加载器实例接口数组和控制处理器实例。并返回一个代理对象。控制处理器实例就是我们实现的orderServiceHandler和userServiceHandler在控制器实例中有我们的增强逻辑。接口数组是要告诉即将生成的代理类要实现哪些接口只需要知道有哪些接口即可不用具体如何实现不用关心。这里我们用的是orderService.getClass().getInterfaces()这里面有OrderService里的所有接口这也是为什么JDK动态代理是基于接口实现的。我们控制打印如下信息观察到两个类的类加载器是完全一样的都是$AppClassLoader一串实例的hashcode。那么我们传入的类加载器实例实际上就是$AppClassLoader63947c6b这个实例的hadcode会变但是类型不变。在同一个项目中会有一个共同的类加载器实例但是事实上还存在其他类加载器BootstrapClassLoader启动类加载器、PlatformClassLoader平台类加载器JDK9、AppClassLoader (应用类加载器)还有容器多模块等场景中都会有一些不同的加载器。代理类是需要和我们的目标类在同一个加载器下而我们的目标类在AppClassLoader下因此需要放在一样的类加载器AppClassLoader下。同时我们观察到接口数组没有打印出来因此使用.toString()可以看到输出的是OrderService接口orderService.getCalss()为OrderServiceImpl,如果OrderServiceImpl实现了多个接口那么输出里也有多个接口。例如还实现了Test接口那么就能拿到注意假如OrderServiceImpl有父类父类实现了接口那么子类虽然也会实现无法通过这种方式拿到。也就是说getInterfaces()只能拿直接实现的接口。然后代理类也会实现所有接口我们才能通过代理类去调用业务。那么控制器什么时候起作用呢当我们的代理实例调用目标方法时首先被控制器拦截进入invoke()方法这个时候我们看invoke()方法的参数已经有了。代理类已经生成好了就是我们前面在做的事情。代理类型是$Proxy0这是JDK动态代理生成的代理类类型没问题后面是toString的结果不用看。method方法是createOrder就是我们的目标方法。参数为目标方法的参数接下来就是前置增强逻辑反射调用了。method.invoke(target,args)这里的target就是引用指向的具体实例orderServiceImpl.那么这个方法就等价于orderServiceImpl.createOrder().然后返回该方法的返回值再执行后置增强逻辑把返回值返回给调用方。5、思考JDK动态代理始终是围绕接口展开的创建动态代理的关键方法Proxy.newInstance()的第二个参数就需要获取实现的接口数组但是getInterfaces()只能拿到直接目标类直接实现的接口。如果接口数组里有多个接口时代理全部会实现接口。代理的返回值源码中写死了是Object,这是所有类的父类因此我们还要强转成我们想要的接口类型选择其中一个接口类型即可。但是一定要注意转换的接口类型必须是接口数组中存在的如果不存在会报$Proxy没有实现接口的错误。一个控制器实例只能创建一个代理实例如果需要给不同的类/对象做代理需要实例化不同的控制器。当没有哪个引用指向这个实例时这个代理就需要被回收了当然还需要满足其他情况这里简写了。四、CGLIB动态代理JDK动态代理要求目标类至少实现一个接口代理和也实现同样的接口代理类和目标类是“兄弟关系”。而在CGLIB中要求目标类无需实现接口代理类和目标类是父子关系代理类是目标类的子类。CGLIBCode Generation Library是一个高性能的代码生成库。它在运行时通过生成目标类的子类来创建代理对象并在子类中重写父类的方法从而在方法执行前后织入增强逻辑。而CGLIB是一个单独的第三方依赖包因此需要引入后才能使用。在我们的SpringBoot项目中spring-core已经包含了因此不需要额外引入。CGLIB动态代理好像适用范围更加广阔我们还是举例子来学习这种代理模式。1、实现CGLIB动态代理OrderService没有实现任何接口设置方法拦截器实现MethodInterceptor接口,这个接口在org.springframework.cglib.proxy.MethodInterceptor2、实现MethoInterceptor方法拦截器这个方法拦截器有点像我们的控制处理器我们把增强的逻辑写在这里。不同的是我们是从这里拿到代理的而控制器则只维护目标对象和inovke的调用。package com.miao.nanonap.service; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class MyMethodInterceptor implements MethodInterceptor { public Object getProxy(Class? clazz) { Enhancer enhancer new Enhancer(); // 增强器 enhancer.setSuperclass(clazz); // 设置父类 enhancer.setCallback(this); // 设置回调即本拦截器 return enhancer.create(); // 创建代理对象 } Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 前置增强逻辑 Long startTime System.currentTimeMillis(); // 调用父类方法比反射快 Object result proxy.invokeSuper(obj, args); // 后置增强 Long endTime System.currentTimeMillis(); System.out.println(method.getName()调用耗时(endTime-startTime)); return result; } }这里的增强器我们之后再看先看如何创建和使用代理。3、调用代理public static void main(String[] args) { // 目标实例 OrderService orderService new OrderService(); // 方法拦截器 MyMethodInterceptor myMethodInterceptor new MyMethodInterceptor(); // 获取子类代理 OrderService orderServiceProxy (OrderService)myMethodInterceptor.getProxy(orderService.getClass()); // 代理调用目标方法 orderServiceProxy.createOrder(10001); }我们输出一下代理的类型System.out.println(orderServiceProxy.getClass());可以看到父类是OrderService代理类是由CGLIB的Enhancer动态生成的。4、代理创建原理现在回到我们的getProxy()和interceptor()方法上来。这个方法的参数是目标类的类型这个类型在设置父类时用到了增强器中设置父类和回调这里的Callback就是“路由器”当方法被执行时代码应该往那边走。Enhancer也是CGLIB提供一种增强器主要用来创建和配置代理。这里我们指定了MyMethodInterceptor就是说当代理的方法被调用时走MyMethodInterceptor的interceptor()方法。然后再看interceptor方法。有几个关键的参数obj就是代理对象本身method当前调用的目标方法的反射对象里面有目标方法的信息args就是目标方法需要的参数数组proxy是调用父类目标方法的“加速器”。这里的method基本我就拿了一个方法名关键在proxy.invokeSuper()方法obj是代理本身是目标类的子类。我们当然可以使用method.invkeobj,args调用目标方法但是invokeSuper()绕过了反射机制没有那么多的安全检查直接生成字节码指令来调用方法。注意method.invoke(目标对象参数数组)proxy.invokeSuper(代理对象参数数组)第一个参数是不同的如果混用了极易造成无穷递归。例如在interceptor中用的是method.invoke(object,args)用了形参里的object那么必将出现执行到这个方法时触发interceptor走到这又触发interceptor会导致栈溢出。反射比调用父类方法有多快呢我给出如下表格调用方式相对耗时直接调用1method.invoke()反射调用5-10proxy.invokeSuper()CGLIB1.2可见CGLIB的调用父类的速度是比反射更快的。我们说代理类是目标类的子类那么必然需要调用构造方法。我们这里没传参数所以调用的是父类的无参构造。恰好父类有默认的无参构造方法如果父类没有无参构造只有有参构造那又该如何做呢再次运行就报错了Exception in thread main java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given父类没有无参构造。那我们就需要在生成代理那块儿把构造方法指定出来public Object getProxy(Class? clazz) { Enhancer enhancer new Enhancer(); // 增强器 enhancer.setSuperclass(clazz); // 设置父类 enhancer.setCallback(this); // 设置回调即本拦截器 return enhancer.create( new Class[]{String.class}, // 参数类型 new Object[]{hello} // 参数值 ); // 创建代理对象 }这里直接把参数写死了但是我们想这个参数到底与我们的业务有意义吗我们只是想通过构造方法拿到代理而已。所以这不重要运行看看。成功了。5、思考CGLIB是一个代码生成库里面除了动态代理还有很多东西。并且动态代理里面还有丰富的CallBack类型分别起着不同的作用。在CGLIB动态代理中我们还需要注意的是。代理子类是继承目标类的那么目标类里的final方法无法被重写代理可以调用但无法加增强逻辑private方法无法被继承代理直接无法调用。还有就是父类的static方法是类的一部分也无法重写。使用继承机制可以提高调用的速度不依赖于接口。但是父类的一些限制也必将导致一些业务在子类中无法调用。五、总结通过学习静态代理、JDK动态代理、CGLIB动态代理我们掌握了不同代理的特点。静态代理在编译期确定需要手写代理类缺点是不同的目标类就需要写不同的代理类而且一开始就被加载了后期造成“类爆炸”。JDK动态代理是基于JDK底层支持的代理模式核心是InvocationHandler控制器处理器的实现和invoke()反射调用满足一定的通用性。但是需要持有目标对象的引用而且要求目标类必须实现接口。CGLIB动态代理则是基于继承创建子类代理它不要求父类必须实现接口。但是需要提供对应的构造方法核心是实现MethodInterceptor方法拦截器在interceptor中织入增强逻辑使用proxy.invokeSuper()调用目标方法。在一般情况下如果某个类实现了接口那么将使用JDK动态代理如果没有实现接口那么就走CGLIB动态代理。这样以后当我们需要往已经写好的业务中织入增强逻辑时就可以考虑使用代理了。