0.参考资料
1.概述
- 为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。—设计模式》GoF
- 核心:
分类
- 静态代理
动态代理
- “增加一层间接层”是软件系统中对许多复杂问题的一-种常见解决方法。在面向对象系统中,直接使用某些对象会带来很多问题, 作为间接层的proxy对象便是解决这一问题的常用手段。
- 具体proxy设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象做细粒度的控制, 如copy-on-write技术, 也可能对组件模块提供抽象代理层,在架构层次对对象做proxy。
Proxy并不一定要求保持接口完整的一致性,只要能够实现间接控制,有时候损及一些透明性是可以接受的。
3.案例
项目
- 某项目中存在一个Calculator类,代表一个计算器,它可以进行加减乘除操作: ```cpp public class Calculator {
// 加 public int add(int a, int b) { int result = a + b; return result; }
// 减 public int subtract(int a, int b) { int result = a - b; return result; }
// 乘法、除法… }
<a name="AMnVx"></a>#### 需求- 在每个方法执行前后打印日志<a name="rnMeK"></a>#### 方案- 硬编码<a name="VF9Mh"></a>#### 代码- 直接修改Calculator类```cpppublic class Calculator {// 加public int add(int a, int b) {System.out.println("add方法开始...");int result = a + b;System.out.println("add方法结束...");return result;}// 减public int subtract(int a, int b) {System.out.println("subtract方法开始...");int result = a - b;System.out.println("subtract方法结束...");return result;}// 乘法、除法...}
分析
4.1静态代理
- 编写一个代理类,实现目标对象相同的接口/父类,并在内部维护一个目标对象的引用。通过构造器塞入目标对象,在代理对象中调用目标对象的同名方法,并添加前拦截,后拦截等所需的业务功能。
方案
- 将Calculator抽取为接口- 创建目标类CalculatorImpl实现Calculator- 创建代理类CalculatorProxy实现Calculator
类图
- ![A)T]IL@9II(W_BG{9`AXRCN.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628576592911-71355d30-b746-44fb-9f9e-1b50e142b988.png#clientId=u4d152ae3-a83c-4&from=paste&height=308&id=u96aadb0d&margin=%5Bobject%20Object%5D&name=A%29T%5DIL%409II%28W_BG%7B9%60AXRCN.png&originHeight=616&originWidth=1085&originalType=binary&ratio=1&size=46085&status=done&style=none&taskId=u60bd8ac1-c043-493a-8516-5aa80b16d7d&width=543)
代码
- 抽取接口
/*** Calculator接口*/public interface Calculator {int add(int a, int b);int subtract(int a, int b);}
- 目标类(实现接口)
/*** 目标类,实现Calculator接口(如果一开始就面向接口编程,其实是不存在这一步的,CalculatorImpl原本就实现Calculator接口)*/public class CalculatorImpl implements Calculator {// 加public int add(int a, int b) {int result = a + b;return result;}// 减public int subtract(int a, int b) {int result = a - b;return result;}// 乘法、除法...}
- 新增代理类(实现接口)
/*** 静态代理类,实现Calculator接口*/public class CalculatorProxy implements Calculator {// 代理对象内部维护一个目标对象引用private Calculator target;// 通过构造方法,传入目标对象public CalculatorProxy(Calculator target) {this.target = target;}// 调用目标对象的add,并在前后打印日志@Overridepublic int add(int a, int b) {System.out.println("add方法开始...");int result = target.add(a, b);System.out.println("add方法结束...");return result;}// 调用目标对象的subtract,并在前后打印日志@Overridepublic int subtract(int a, int b) {System.out.println("subtract方法开始...");int result = target.subtract(a, b);System.out.println("subtract方法结束...");return result;}// 乘法、除法...}
- 测试
public class Test {public static void main(String[] args) {// 把目标对象通过构造器塞入代理对象Calculator calculator = new CalculatorProxy(new CalculatorImpl());// 代理对象调用目标对象方法完成计算,并在前后打印日志calculator.add(1, 2);calculator.subtract(2, 1);}}
add方法开始...add方法结束...subtract方法开始...subtract方法结束...
分析
静态代理的优点:可以在不修改目标对象的前提下,对目标对象进行功能的扩展和拦截。但是它也仅仅解决了上一种方案4大缺点中的第1、4两点:硬编码的缺点. 还剩下 代理类中重复编码的问题.
- 直接修改源程序,不符合开闭原则,即好的程序设计应该对扩展开放,对修改关闭(✅,如果一开始就面向接口编程,这一步其实是不需要的)- 如果Calculator类内部有几十个、上百个方法,修改量太大(❎,目标类有多少个方法,代理类就要重写多少个方法)- 存在重复代码(都是在核心代码前后打印日志)(❎,代理类中的日志代码是重复的)- 日志打印硬编码在代理类中,不利于后期维护:比如你花了一上午终于写完了,组长告诉你这个功能不做了(✅,别用代理类就好了)
静态代理的问题
上面的代码中,为了给目标类做日志增强,我们编写了代理类,而且准备了一个构造器接收目标对象。代理代理对象构造器的参数类型是Calculator,这意味着它只能接受Calculator的实现类对象,亦即我们写的代理类CalculatorProxy只能给Calculator做代理,它们绑定死了!
- 如果现在我们系统需要全面改造,要给其他类也添加日志打印功能,就得为其他几百个接口都**各自**写一份代理类...- 
- 自己手动写一个类并实现接口实在太麻烦了。仔细一想,我们其实想要的并不是代理类,而是代理对象!有没有一个方法,我传入接口+增强的代码(比如打印日志),它就给我自动返回代理对象呢?这样就能省去编写代理类这个无用的“中介”了
4.2动态代理
需求分析
- 为什么打印日志的代码无法复用呢?因为打印日志的代码被直接硬编码在代理类中,和代理类是耦合的!- 分析前两版本, 从修改原代码到引入静态代理,**其实就是趋向解耦的过程:**- 原本我们把代码直接写在目标类中,**日志代码和目标类耦合**了,所以一旦需求被撤销,你又要去修改目标类- 静态代理则把日志代码**抽取**出来,放在代理类中,解决了增强代码和目标类的耦合,但又造成了**增强代码和代理类的耦合**,所以**代理类无法复用**,有多少个目标类就要写多少个代理类
现阶段待解决的问题
- 动态代理如果要优于静态代理,至少需要解决两个问题:- **①自动生成代理对象(实例)**- ②将增强代码与代理类(代理对象)解耦,从而达到代码复用- 
问题思路
1.如何不写代理类
- [类加载时到生成对象过程](https://www.yuque.com/ryze_java/javase/hebaus#AFRUd). 可以看出,要创建一个实例,最关键的就是**得到对应的Class对象.**- 回到我们之前的问题:如何不写代理类,直接得到代理对象。按照[类加载时到生成对象过程](https://www.yuque.com/ryze_java/javase/hebaus#AFRUd),代理类和实例对象之间其实还隔着一个Class对象。**如果能得到Class对象,就能生成实例。**所以,现在的问题又变成:**如何不写代理类,直接得到Class对象。**- 其中: 我们得知Class对象只能由JVM创建。虽然不能new,但Java还是提供了其他方式让我们得到Class对象,底层会告诉JVM帮我们创建:1. Class.forName(xxx):Class<Person> clazz = Class.forName("com.bravo.Person");1. xxx.class:Class<Person> clazz = Person.class;1. xxx.getClass():Class<Person> clazz = person.getClass();- 问题: **这三种方式都需要先有类,但我们不想编写代理类**!
2.从接口寻求突破口
- 代理类或者代理对象重要吗?它其实只是个空壳,最重要的其实是 增强代码 + 目标对象。我们对代理对象的要求很低,只需要与目标对象拥有相同的方法即可。如此一来,别人调用proxy.add()得到的效果和调用target.add()是一样的,甚至因为两者都实现了相同接口,用接口类型接收后,calculator.add()根本分不出是代理还是原对象。- 所以本质上,代理对象只要有方法申明即可,甚至不需要方法体,或者只要一个空的方法体即可,反正我们会把目标对象返回去。- 有两个途径:- 目标类本身. CGLib动态代理- 目标类实现的接口. JDK动态代理- 这两个思路造就了两种不同的代理机制,本文重点介绍实现接口的JDK动态代理。
3.验证从接口获取方法信息
- 测试类
// 在静态代理类的同环境下测试public class ProxyTest {public static void main(String[] args) {/*** Calculator接口的Class对象* 得到Class对象的三种方式:* 1.Class.forName(xxx)* 2.xxx.class* 3.xxx.getClass()* 注意,这并不是我们new了一个Class对象,而是让虚拟机加载并创建Class对象*//*** Calculator接口的Class对象*/Class<Calculator> calculatorClazz = Calculator.class;//Calculator接口的构造器信息Constructor<?>[] calculatorClazzConstructors = calculatorClazz.getConstructors();//Calculator接口的方法信息Method[] calculatorClazzMethods = calculatorClazz.getMethods();//打印System.out.println("------接口Class的构造器信息------");printClassInfo(calculatorClazzConstructors);System.out.println("\n");System.out.println("------接口Class的方法信息------");printClassInfo(calculatorClazzMethods);System.out.println("\n");/*** Calculator实现类的Class对象*/Class<CalculatorImpl> calculatorImplClazz = CalculatorImpl.class;//Calculator实现类的构造器信息Constructor<?>[] calculatorImplClazzConstructors = calculatorImplClazz.getConstructors();//Calculator实现类的方法信息Method[] calculatorImplClazzMethods = calculatorImplClazz.getMethods();//打印System.out.println("------实现类Class的构造器信息------");printClassInfo(calculatorImplClazzConstructors);System.out.println("\n");System.out.println("------实现类Class的方法信息------");printClassInfo(calculatorImplClazzMethods);}/*** @description: 参数类型Executable是Mehode和Constructor共同的父类* @param: targets* @return: void* @date: 2021/8/10 16:57*/public static void printClassInfo(Executable[] targets) {for (Executable target : targets) {StringBuilder stringBuilder = new StringBuilder();String targetName = target.getName();stringBuilder.append(targetName);stringBuilder.append("(");Class<?>[] parameterTypes = target.getParameterTypes();for (Class<?> parameterType : parameterTypes) {String parameterTypeName = parameterType.getName();stringBuilder.append(parameterTypeName).append(",");}if (parameterTypes.length != 0 && parameterTypes != null){stringBuilder.deleteCharAt(stringBuilder.length()-1);}stringBuilder.append(")");System.out.println(stringBuilder);}/*for (Executable target : targets) {// 构造器/方法名称String name = target.getName();StringBuilder sBuilder = new StringBuilder(name);// 拼接左括号sBuilder.append('(');Class<?>[] clazzParams = target.getParameterTypes();// 拼接参数for (Class<?> clazzParam : clazzParams) {sBuilder.append(clazzParam.getName()).append(',');}//删除最后一个参数的逗号if (clazzParams.length != 0) {sBuilder.deleteCharAt(sBuilder.length() - 1);}//拼接右括号sBuilder.append(')');//打印 构造器/方法System.out.println(sBuilder.toString());}*/}}
- 结果
------接口Class的构造器信息------------接口Class的方法信息------add(int,int)subtract(int,int)------实现类Class的构造器信息------com.ryze.newProxy1.CalculatorImpl()------实现类Class的方法信息------add(int,int)subtract(int,int)wait()wait(long,int)wait(long)equals(java.lang.Object)toString()hashCode()getClass()notify()notifyAll()
- 得到以下结论:- **接口Class对象没有构造方法**,所以Calculator接口不能直接new对象- 实现类Class对象有构造方法,所以CalculatorImpl实现类可以new对象- 接口Class对象有两个方法add()、subtract()- 实现类Class对象除了add()、subtract(),还有从Object继承的方法(可用其它方法返回子类特有的所有方法)- 也就是说,接口Class的对象和实现类的Class对象除了构造器,其他信息基本相似- 至此,我们至少知道从接口获取方法信息是可能的!接下来的努力方向就是:**怎么根据一个接口得到代理对象。**
引出JDK动态代理
- 通过刚才的实验,我们不仅知道了接口确实包含我们所需要的方法信息,还知道了接口为什么不能直接new对象:接口缺少构造器信息。那么,是否存在一种机制,能给接口安装上构造器呢?或者,不改变接口本身,直接拷贝接口的信息到另一个Class,然后给那个Class装上构造器呢?- 很显然,不论是从开闭原则还是常规设计考虑,直接修改接口Class的做法相对来说不是很合理。JDK选择了后者:拷贝接口Class的信息,产生一个新的Class对象。- **也就是说,JDK动态代理的本质是:用Class造Class,即用接口Class造出一个代理类Class。**- - 具体API: java.lang.reflect.Proxy中- public static Class<?> _getProxyClass_(ClassLoader loader,Class<?>... interfaces) : 返回代理类的Class对象, 并向其提供类加载器和接口信息- - 也就说,只要传入接口的Class对象,getProxyClass()方法即可返回代理Class对象,而不用实际编写代理类。即跳过了代理类的编写!- 
代码
- 测试 Proxy.getProxyClass(...) : Class<?>
public class ProxyTest {public static void main(String[] args) {/** 参数1:Calculator的类加载器(当初把Calculator加载进内存的类加载器)* 参数2:代理对象需要和目标对象实现相同接口Calculator* */Class<?> calculatorProxyClazz = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);//以Calculator实现类的Class对象作对比,看看代理Class是什么类型System.out.println(CalculatorImpl.class.getName());System.out.println(calculatorProxyClazz.getName());//打印代理Class对象的构造器Constructor<?>[] constructors = calculatorProxyClazz.getConstructors();System.out.println("----构造器----");printClassInfo(constructors);System.out.println("\n");//打印代理Class对象的方法Method[] methods = calculatorProxyClazz.getMethods();System.out.println("----方法----");printClassInfo(methods);System.out.println("\n");}public static void printClassInfo(Executable[] targets) {for (Executable target : targets) {// 构造器/方法名称String name = target.getName();StringBuilder sBuilder = new StringBuilder(name);// 拼接左括号sBuilder.append('(');Class<?>[] clazzParams = target.getParameterTypes();// 拼接参数for (Class<?> clazzParam : clazzParams) {sBuilder.append(clazzParam.getName()).append(',');}//删除最后一个参数的逗号if (clazzParams.length != 0) {sBuilder.deleteCharAt(sBuilder.length() - 1);}//拼接右括号sBuilder.append(')');//打印 构造器/方法System.out.println(sBuilder.toString());}}}
- 结果
com.ryze.newProxy2.CalculatorImplcom.sun.proxy.$Proxy0----构造器----com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)----方法----add(int,int)equals(java.lang.Object)toString()hashCode()subtract(int,int)isProxyClass(java.lang.Class)getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)getInvocationHandler(java.lang.Object)newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)wait()wait(long,int)wait(long)getClass()notify()notifyAll()
- 结论- Proxy.getProxyClass()返回的Class对象是有构造器的!- 返回的Class对象是 Class<Proxy>- - [使用动态代理需要解决问题](#moPcL). 现在我们已经得到了代理Class,只需通过反射即可得到代理对象。
动态代理的底层逻辑
- 我们已经顺利通过接口得到代理Class对象,有了代理Class对象意味着代理对象唾手可得。- [观察代理类的构造器信息](https://www.yuque.com/ryze_java/design_pattern/nzse8l?inner=buTU9), 只有一个构造器,唯一参数InvocationHandler.- com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
代码
- 简单的动态代理. 成功获取代理对象并执行目标方法
/**version: 1.0* @description: 简单的动态代理(还未有具体的目标类)*/public class ProxyTest {public static void main(String[] args) throws Exception {/** 参数1:ClassLoader :传入系统类加载器* 参数2:Class<?>... :需要生成代理Class的接口Class类型* */Class<?> calculatorProxyClazz = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);// 得到唯一的有参构造 $Proxy0(InvocationHandler h),和反射的Method有点像,可以理解为得到对应的构造器执行器Constructor<?> constructor = calculatorProxyClazz.getConstructor(InvocationHandler.class);// 用构造器执行器执行构造方法,得到代理对象。构造器需要InvocationHandler入参Calculator calculatorProxyImpl = (Calculator) constructor.newInstance(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return 10086;}});// 执行代理类的同名方法System.out.println(calculatorProxyImpl.add(1, 2)); // 10086}}
InvocationHandler解析
- 从实验结果看,会发现每次调用代理对象的方法,最终都会调用InvocationHandler的invoke()方法- - 理解:- 根据代理Class的构造器创建对象时,需要传入InvocationHandler。Proxy的内部确实有个成员变量: java.lang.reflect.Proxy类源码:
:::info
protected InvocationHandler h; // JDK中描述: 代理实例的调用处理器
private Proxy() { // 私有默认构造
}
protected Proxy(InvocationHandler h) {
Objects._requireNonNull(h);
this.h = h;
}
:::
- 而且代理对象的每个方法内部都会调用h.invoke(). 也就是说,动态代理为了实现代理对象和增强代码的解耦,把增强代码也抽取出去了,让InvocationHandler作为它与目标对象的桥梁。- 
JDK动态代理最终生成的其实是Class
,最终的代理对象是proxy对象,而且实现了Calculator接口。
invoke(Object proxy, Method method, Object[] args) : Object 方法解析
参数
- Object proxy:代理对象本身,而不是目标对象(不要调用,会无限递归,一般不会使用)- Method method:目标类的目标方法. 方法执行器,用来执行方法(有点不好解释,Method只是一个执行器,传入目标对象就执行目标对象的方法)- Obeject[] args:方法参数
问题与解释
- 问: 形参中没有具体的(被代理)目标对象.- 答: ???
动态代理的代码实现
1简单的invoke() 内部new目标对象
- 代码
/**version:1* @description: 一个最基本的动态代理(已经有一个基本的目标实体类)*/public class ProxyTest {public static void main(String[] args) throws Exception {/** 参数1:类加载器,随便给一个* 参数2:需要生成代理Class的接口,比如Calculator* */Class<?> calculatorProxyClazz = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);// 得到唯一的有参构造 $Proxy(InvocationHandler h),和反射的Method有点像,可以理解为得到对应的构造器执行器Constructor<?> constructor = calculatorProxyClazz.getConstructor(InvocationHandler.class);// 用构造器执行器执行构造方法,得到代理对象。构造器需要InvocationHandler入参Calculator calculatorProxyImpl = (Calculator) constructor.newInstance(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Calculator calculator = new CalculatorImpl();System.out.println(method.getName() + "() 目标类的方法开始执行...");Object result = method.invoke(calculator, args[0], args[1]);System.out.println(result);System.out.println(method.getName() + "() 目标类的方法执行结束...");return result;}});// 执行代理类的同名方法calculatorProxyImpl.add(1, 2);}}
- 测试结果
add() 目标类的方法开始执行...3add() 目标类的方法执行结束...
2.抽取方法,简化操作
- 代码
/**version:2* 外部传入目标对象, 简化invoke(...)内部责任*/public class ProxyTest {public static void main(String[] args) throws Throwable {// 后期需要一个具体的目标类, 和目标类的接口. 故如此一对象二用CalculatorImpl target = new CalculatorImpl();// 传入目标对象Calculator calculatorProxy = (Calculator) getProxy(target);// 执行代理方法calculatorProxy.add(1, 2);}/*** 传入目标对象,获取代理对象** @param target 目标类的接口实例* @return* @throws Exception*/private static Object getProxy(final Object target) throws Exception {/**获取代理类的Class对象.其中:参数二需要接口数组, 因 target 是具体实现实例(毕竟不能new 接口), 故此代码.若二参填: target.getClass(),则报: java.lang.IllegalArgumentException: com.ryze.newProxy3.CalculatorImpl is not an interface* */Class<?> proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());// 获取唯一的构造器Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);return constructor.newInstance(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(method.getName() + "方法开始执行...");Object result = method.invoke(target, args);System.out.println(result);System.out.println(method.getName() + "方法执行结束...");return result;}});}}
- 测试结果
add方法开始执行...3add方法执行结束...
3.解耦代理对象与增强代码
- 上面的代码还有问题:虽然传入任意对象我们都可以返回增强后的代理对象,但增强代码是写死的。如果我需要的增强不是打印日志而是其他操作呢?难道重新写一个getProxy()方法吗?所以,我们应该抽取InvocationHander,将增强代码和代理对象解耦(其实重写getProxy()和抽取InvocationHander本质相同,但后者细粒度小一些)。- 代码
// version :3public class ProxyTest {public static void main(String[] args) throws Throwable {// 1.得到目标对象CalculatorImpl target = new CalculatorImpl();// 2.传入目标对象,得到增强对象(如果需要对目标对象进行别的增强,可以另外编写getXxInvocationHandler)InvocationHandler logInvocationHandler = getLogInvocationHandler(target);// 3.传入目标对象+增强代码,得到代理对象Calculator calculatorProxy = (Calculator) getProxy(target, logInvocationHandler);calculatorProxy.add(1, 2);}/*** 传入目标对象+增强代码,获取代理对象** @param target* @param handler* @return* @throws Exception*/private static Object getProxy(final Object target, final InvocationHandler handler) throws Exception {// 参数1:随便找个类加载器给它 参数2:需要代理的接口Class<?> proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);return constructor.newInstance(handler);}/*** 日志增强代码** @param target* @return*/private static InvocationHandler getLogInvocationHandler(final CalculatorImpl target) {return new InvocationHandler() {@Overridepublic Object invoke(Object proxy1, Method method, Object[] args) throws Throwable {System.out.println(method.getName() + "方法开始执行...");Object result = method.invoke(target, args);System.out.println(result);System.out.println(method.getName() + "方法执行结束...");return result;}};}}
- 测试结果: 正常, 略
4.优化代码语义
- 上面的代码抽取了两个方法,仔细观察你会发现,getLogInvocationHandler(target)的target参数是必要的,但getProxt(target, invocationHandler)的target参数是没必要的:- 首先,getProxy()其实只需要知道要实现的接口是什么,就能返回该接口的代理对象不是吗?- 其次,invocationHandler已经包含目标对象- 测试代码- 代码
// version: 4public class ProxyTest {public static void main(String[] args) throws Throwable {// 1.得到目标对象CalculatorImpl target = new CalculatorImpl();// 2.传入目标对象,得到增强对象(如果需要对目标对象进行别的增强,可以另外编写getXxInvocationHandler)InvocationHandler logInvocationHandler = getLogInvocationHandler(target);// 3.传入接口+增强对象(含目标对象),得到代理对象Calculator calculatorProxy = (Calculator) getProxy(logInvocationHandler, // 增强对象(包含 目标对象 + 增强代码)target.getClass().getClassLoader(), // 随便传入一个类加载器target.getClass().getInterfaces() // 需要代理的接口);calculatorProxy.add(1, 2);}/*** 传入接口+增强(已经包含了目标对象),获取代理对象** @param handler* @param classLoader* @param interfaces* @return* @throws Exception*/private static Object getProxy(final InvocationHandler handler, final ClassLoader classLoader, final Class<?>... interfaces) throws Exception {// 参数1:随便找个类加载器给它 参数2:需要代理的接口Class<?> proxyClazz = Proxy.getProxyClass(classLoader, interfaces);Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);return constructor.newInstance(handler);}/*** 日志增强代码** @param target* @return*/private static InvocationHandler getLogInvocationHandler(final CalculatorImpl target) {return new InvocationHandler() {@Overridepublic Object invoke(Object proxy1, Method method, Object[] args) throws Throwable {System.out.println(method.getName() + "方法开始执行...");Object result = method.invoke(target, args);System.out.println(result);System.out.println(method.getName() + "方法执行结束...");return result;}};}}
- 测试结果: 正常, 略
5.final版本: 更好用的API:Proxy.newProxyInstance()
- 目前为止,我们学习都是Proxy.getProxyClass():- 先获得proxyClazz- 再根据proxyClazz.getConstructor()获取构造器- 最后constructor.newInstance()生成代理对象- 为了简化代码,我们把这三步封装为getProxy()方法。然而,其实JDK已经提供了一步到位的方法Proxy.newProxyInstance(),你会发现它的参数和我们上面最终封装的getProxy()是一样的:
:::info
newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) : Object
:::
- 直接使用JDK封装的newProxyInstance()- 代码测试- 代码
//version: finalpublic class ProxyTest {public static void main(String[] args) throws Throwable {// 1.得到目标对象CalculatorImpl target = new CalculatorImpl();// 2.传入目标对象,得到增强对象(如果需要对目标对象进行别的增强,可以另外编写getXxInvocationHandler)InvocationHandler logInvocationHandler = getLogInvocationHandler(target);// 3.传入目标对象+增强代码,得到代理对象(直接用JDK的方法!!!)Calculator calculatorProxy = (Calculator) Proxy.newProxyInstance(target.getClass().getClassLoader(), // 随便传入一个类加载器target.getClass().getInterfaces(), // 需要代理的接口logInvocationHandler // 增强对象(包含 目标对象 + 增强代码));calculatorProxy.add(1, 2);}/*** 日志增强代码** @param target* @return*/private static InvocationHandler getLogInvocationHandler(final CalculatorImpl target) {return new InvocationHandler() {@Overridepublic Object invoke(Object proxy1, Method method, Object[] args) throws Throwable {System.out.println(method.getName() + "方法开始执行...");Object result = method.invoke(target, args);System.out.println(result);System.out.println(method.getName() + "方法执行结束...");return result;}};}}
- 结果正常:略- 至此,我们又完成了动态代理的第二个目标:代码复用。不仅目标对象与增强代码解耦,代理对象也和增强代码解耦了
5.总结
5.1Proxy
- JDK根据接口生成的其实是Proxy的Class对象(即 Class<Proxy> ),然后根据proxyClass(.getConstructor(...).newInstance(...) : Object)得到proxy代理对象. proxy代理对象实现了接口,同时也是Proxy类型的。- proxy对象的原理是:内部维护一个InvocationHandler,而InvocationHandler是对增强代码的抽象。通过抽取InvocationHandler,将代理对象和增强代码解耦。- 其实就是调用链路拉长了,原本代理对象直接调用目标对象,现在是代理对象调InvocationHandler,InvocationHandler再调目标对象。Proxy代理对象内部有InvocationHandler对象,而InvocationHandler对象内部有我们塞进去的目标对象,所以最终通过代理对象可以调用到目标对象,并且得到了增强。
.经典使用
1.Spring中AOP
- 在Spring的AOP编程中:- 如果加入容器的目标对象有实现接口,用JDK代理- 如果目标对象没有实现接口,用Cglib代理
2.MyBatis中
- 通过Map文件的namespace绑定对应的DAO层接口, 通过sql标签的id属性绑定具体方法. 会动态生成具体DAO接口的运行时实现子类完成业务- 

