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类
```cpp
public 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,并在前后打印日志
@Override
public int add(int a, int b) {
System.out.println("add方法开始...");
int result = target.add(a, b);
System.out.println("add方法结束...");
return result;
}
// 调用目标对象的subtract,并在前后打印日志
@Override
public 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.CalculatorImpl
com.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() {
@Override
public 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() {
@Override
public 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() 目标类的方法开始执行...
3
add() 目标类的方法执行结束...
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() {
@Override
public 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方法开始执行...
3
add方法执行结束...
3.解耦代理对象与增强代码
- 上面的代码还有问题:虽然传入任意对象我们都可以返回增强后的代理对象,但增强代码是写死的。如果我需要的增强不是打印日志而是其他操作呢?难道重新写一个getProxy()方法吗?所以,我们应该抽取InvocationHander,将增强代码和代理对象解耦(其实重写getProxy()和抽取InvocationHander本质相同,但后者细粒度小一些)。
- 代码
// version :3
public 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() {
@Override
public 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: 4
public 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() {
@Override
public 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: final
public 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() {
@Override
public 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接口的运行时实现子类完成业务
- 