Java之动态代理 DynamicProxy

Java之动态代理 DynamicProxy
动态代理概念在学习动态代理之前先了解静态代理可参考静态代理静态代理核心缺陷类爆炸一个接口实现类必须手写一个专属代理类业务类越多代理类文件成倍增加增强逻辑无法复用日志、耗时统计等通用逻辑在每个代理类重复复制修改需要改动全部代理接口迭代维护成本极高接口新增方法所有业务类、全部静态代理类必须同步重写极易漏改运行时无法灵活适配代理类编译前手写固定无法根据运行时参数动态切换代理目标。动态代理的核心设计目标不用手动编写代理类程序运行阶段通过反射 / 字节码技术自动生成代理对象统一拦截所有目标方法一套增强逻辑适配同一接口下全部实现类彻底解决静态代理所有短板。动态代理两大分类类型依赖限制条件底层原理适用场景JDK 动态代理JDK 原生反射包目标类必须实现接口运行时生成实现目标接口的代理类有接口的 Service、Mapper、Feign 远程接口CGLIB 动态代理第三方字节码包spring 内置目标类不能是 final方法不能 final运行时生成目标类的子类重写业务方法无接口的普通业务类本文章主要以JDK 动态代理讲解动态代理核心概念JDK 动态代理依靠java.lang.reflect反射包实现运行时动态生成实现目标接口的匿名代理类所有代理对象的方法调用都会统一转发至InvocationHandler的invoke方法拦截开发者在该方法统一编写前置 / 后置增强逻辑。三大核心 API1InvocationHandler拦截处理器核心public interface InvocationHandler { /** * 代理方法统一拦截入口 * param proxy 自动生成的代理对象 * param method 当前被调用的目标方法反射对象 * param args 调用方法传入的参数数组 * return 方法执行返回值 */ Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }invoke参数讲解proxy动态生成的代理对象本身极少使用仅用于区分代理实例method本次触发调用的方法反射对象唯一标识当前执行的业务方法args调用方法传入的参数无参方法则为null。2Proxy代理生成工具类public static Object newProxyInstance(ClassLoader loader, Class?[] interfaces, InvocationHandler h)ClassLoader loader类加载器使用目标对象的类加载器用于加载动态生成的代理类Class?[] interfaces目标类实现的接口数组代理类会全部实现这些接口保证多态互换InvocationHandler h自定义拦截处理器绑定到代理对象所有方法调用转发至该处理器。3Method反射方法对象封装当前调用的方法信息方法名、参数、返回值、所属类用于反射执行真实目标方法。结合代码彻底理解package demo1.basic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Scanner; public class DynamicAgent { public static void main(String[] args) { Scanner scanner new Scanner(System.in); System.out.println( 动态代理测试菜单 ); System.out.println(1Kobe 2Jordan 3pyLiu); System.out.print(请选择球员); int choice scanner.nextInt(); NBA target null; switch (choice) { case 1: target new Kobe(); break; case 2: target new Jordan(); break; case 3: target new pyLiu(); break; default: System.out.println(无效选择); return; } // 绑定拦截处理器 DogSon dogSon new DogSon(); dogSon.getTarget(target); NBA proxy (NBA) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), dogSon ); // 测试1无参无返回值方法 personalAction() System.out.println(\n---------- 调用无参方法 personalAction ----------); proxy.personalAction(); // 测试2多参数、无返回值方法 score(int, String) System.out.println(\n---------- 调用双参数无返回方法 score ----------); proxy.score(81, 季后赛单场最高分); // 测试3带参数、有返回值方法 getCareerData(int) System.out.println(\n---------- 调用带参有返回方法 getCareerData ----------); String data proxy.getCareerData(2010); System.out.println(【主线程接收方法返回值】 data); } // 自定义拦截处理器 static class DogSon implements InvocationHandler { private NBA target; void getTarget(NBA target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName method.getName(); // 1、区分不同方法差异化前置增强 if (personalAction.equals(methodName)) { // 无参方法args null if (target instanceof Kobe) { System.out.print(What can I say ); } else if (target instanceof Jordan) { System.out.print(GOAT ); } else if (target instanceof pyLiu) { System.out.print(你抽中了隐藏款 ); } } else if (score.equals(methodName)) { // 带参方法打印args数组查看参数传递效果 System.out.print(【score方法前置】当前传入参数); for (Object arg : args) { System.out.print(arg ); } System.out.print( | 球员 target.getClass().getSimpleName() ); } else if (getCareerData.equals(methodName)) { System.out.print(【getCareerData方法前置】查询年份 args[0] ); } // 打印目标类名 System.out.println( target.getClass().getSimpleName()); // 2、反射执行业务方法 // 统一一行代码适配无参/多参、有返回/无返回所有方法 Object result method.invoke(target, args); // 3、后置增强处理返回值 if (result ! null) { System.out.println(【后置增强】业务方法返回内容 result); } else { System.out.println(【后置增强】该方法无返回值void); } return result; // 把业务返回值向上传递给调用方 } } // 统一业务接口3种方法覆盖 无参void、多参void、带参有返回值 interface NBA { // 无参数、无返回值 void void personalAction(); // 多个参数、无返回值 void void score(int point, String desc); // 单个参数、有返回值 String String getCareerData(int year); } // Kobe 实现类 static class Kobe implements NBA { Override public void personalAction() { System.out.println(湖人总冠军); } Override public void score(int point, String desc) { System.out.println(科比拿下 point 分记录 desc); } Override public String getCareerData(int year) { if (year 2010) { return 2010年科比拿下总冠军FMVP; } return 科比生涯数据; } } // Jordan 实现类 static class Jordan implements NBA { Override public void personalAction() { System.out.println(黑人乔丹); } Override public void score(int point, String desc) { System.out.println(乔丹拿下 point 分记录 desc); } Override public String getCareerData(int year) { return 乔丹6冠历史第一人; } } // pyLiu 实现类 static class pyLiu implements NBA { Override public void personalAction() { System.out.println(暴扣乔丹); } Override public void score(int point, String desc) { System.out.println(我大学室友 point 分记录 desc); } Override public String getCareerData(int year) { return 上理第一给; } } }建议粘贴到idea查看观察输入输出主要针对三种不同方案进行分别讲解公共前置流程程序启动控制台输入选择1创建被代理对象target new Kobe()创建拦截处理器实例DogSon dogSon new DogSon()将真实球员对象注入处理器dogSon.getTarget(target)调用Proxy.newProxyInstance()JVM 在内存自动生成动态代理类$Proxy0返回代理对象proxy走完以上步骤后分三条独立链路执行无参方法、有参方法、带返回值方法。1无参方法主线执行proxy.personalAction()自动生成的代理类$Proxy0捕获该方法调用封装三组参数proxy当前代理实例method对应personalAction()的反射方法对象argsnull无入参执行dogSon.invoke(proxy, method, null)进入拦截逻辑invoke 内部逻辑通过method.getName()判断当前是personalAction打印Kobe前置文案打印目标类名Kobe执行反射调用method.invoke(target, null)触发Kobe.personalAction()输出业务内容该方法是 void反射执行结果为null后置打印「无返回值」return null 逐层回传给主线程主线无变量接收2有参方法主线执行proxy.score(81, 季后赛单场最高分)代理类捕获调用封装三组参数proxy当前代理实例method对应score(int,String)的反射对象argsnew Object[]{81, 季后赛单场最高分}入参封装成数组执行dogSon.invoke(proxy, method, args)invoke 内部逻辑通过方法名识别为score循环遍历 args 数组打印所有传入参数打印目标类名Kobe执行method.invoke(target, args)数组内参数自动传给业务方法执行Kobe.score(81, 季后赛单场最高分)void 方法执行结果为 null后置打印「无返回值」return null 回传给主线程3有返回值方法主线执行String data proxy.getCareerData(2010)代理类捕获调用封装三组参数proxy当前代理实例method对应getCareerData(int)的反射对象argsnew Object[]{2010}执行dogSon.invoke(proxy, method, args)invoke 内部逻辑通过方法名识别为getCareerData读取args[0]获取查询年份打印前置日志打印目标类名Kobe执行method.invoke(target, args)调用Kobe.getCareerData(2010)得到返回字符串2010年科比拿下总冠军FMVP后置增强打印业务返回内容将字符串结果return向上传递主线程接收返回值存入变量data打印输出个人学习问题解惑1、代理类proxy在调用方法如.personalAction()时是如何把方法名传给拦截器DogSon中的invoke函数中的method简单来说就是我拦截器的invoke是怎么知道要执行personalAction()这个方法的通过查询在调用(NBA) Proxy.newProxyInstance时会在内存自动生成一个实现NBA接口的代理类这个类是看不见的伪代码如下// JDK自动生成的代理类 $Proxy0 implements NBA public class $Proxy0 implements NBA { // 构造时传入你写的DogSon拦截器 private final InvocationHandler h; public $Proxy0(InvocationHandler h) { this.h h; } // 重写接口无参方法 personalAction Override public void personalAction() { try { // 1. 提前预加载好当前方法的Method对象固定写死在代理类字节码里 Method m NBA.class.getDeclaredMethod(personalAction); // 2. 直接把method、代理对象、参数打包传给invoke h.invoke(this, m, null); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } // 重写接口有参方法 score Override public void score(int point, String desc) { try { Method m NBA.class.getDeclaredMethod(score, int.class, String.class); // 调用时传入的81、季后赛单场最高分 自动封装成数组args Object[] args new Object[]{point, desc}; h.invoke(this, m, args); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } // 重写带返回值方法 getCareerData Override public String getCareerData(int year) { try { Method m NBA.class.getDeclaredMethod(getCareerData, int.class); Object[] args new Object[]{year}; // 接收invoke返回的结果向上返回给主线程 Object res h.invoke(this, m, args); return (String) res; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } }可以看到隐式的代理类$Proxy0重写了NBA接口的所有方法显式的Proxy在构造时将拦截器InvocationHandler传给了隐式的代理类$Proxy0而$Proxy0中在每个方法里都指明了Method并在调用拦截器的invoke方法时将this也就是Proxymethodargs都传给了拦截器中的invoke方法用以执行。以上内容纯属个人经验帖欢迎讨论查漏补缺。