引用
整理自阅读bravo的java小册专栏《JDK动态代理》笔记:
- https://www.yuque.com/bravo1988/primary/admic4
从一个需求引入:为方法增加打印日志的功能
直接修改源代码的缺点
- 符合开闭原则,即好的程序设计应该对扩展开放,对修改关闭
- 修改量太大,当有多个类及方法时
- 存在重复代码
- 日志打印硬编码在代理类中,不利于后期维护(当需要撤销功能时)
静态代理实现
- 解决缺点1,4
- 静态代理则把日志代码抽取出来,放在代理类中,解决了增强代码和目标类的耦合,但又造成了增强代码和代理类的耦合,所以代理类无法复用,有多少个目标类就要写多少个代理类
- 实现一个代理类,实现和目标类一样的接口。通过构造方法将目标类对象作为参数传入到代理类中,即在代理类的内部维护一个目标对象引用。现在可以重写增强方法,在增强方法里面只写增强代码,其它部分可以调用目标类的方法。
mark: 我们真正想要的其实不是代理类,而是最后的代理对象。
动态代理的实现
相比静态代理
- 自动生成代理对象,让程序员免受编写代理类的痛
- 将增强代码与代理类(代理对象)解耦,从而达到代码复用
复习知识,类对象以及类是如何创建的
- Class对象只能由JVM创建。虽然不能new,但Java还是提供了其他方式让我们得到Class对象,底层会告诉JVM帮我们创建
a. Class.forName(xxx):Class
clazz = Class.forName("com.bravo.Person"); b. xxx.class:Class clazz = Person.class; c. xxx.getClass():Class clazz = person.getClass(); 这3种方式都要求先有类,但我们并不想编写代理类
思考:如何自动生成代理对象? 要得到一个对象,首先要得到对应的Class对象(我们new一个对象,背后是JVM使用改类的Class对象的相应方法给我们创建并返回) 我们想不写代理类,直接得到Class对象,该如何做到呢? 上面复习类的创建方法,都需要先存在类。我们并不想写代理类,因为我们想要的至少代理类对象。该怎么做? 从接口入手。 回归本质,我们想要的是:增强代码+目标对象;代理类,或者代理对象,都只是实现的手段 所以现在,我们的思路通过接口来创建代理对象。
无论如何,要创建对象,需要类(接口实际是不能直接创建的)。如何获得类信息呢?
- 目标类本身
- 目标类实现的接口 这两种思路造成了两种不同的代理思路,一个被后人称为CGLib动态代理,另一个则被JDK收录,世人称为JDK动态代理。下面我们继续按照我们的思路,介绍JDK动态代理的实现。 接口Class对象和实现类的Class对象,除了实现类Class对象拥有构造器(以及从Object继承的方法),其它信息基本相似
根据目前掌握的信息,引入JDK动态代理,只要给接口Class对象提供构造器,似乎就能解决了? 直接修改接口Class对象不符合开闭原则,JDK选择拷贝接口信息到另一个Class,并由那个Class提供构造器。是的,这就Proxy类。
Proxy.getProxyClass():返回代理类的Class对象。只要传入接口Class对象,getProxyClass( )方法即可返回代理对象,而不用实际编写代理类,即下图所示:
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());
}
}
}
现在,我们以及可以不通过实现接口实现类,而得到代理Class对象。这意味我们只需要通过反射,即可得到代理对象。 对比代理Classd对象和原来接口Class对象的信息:
现在我们有了构造器了,但似乎我们还没解决怎么“增强”这件事? 查看我们创建的代理类Class对象拥有构造器: 可以看到
-
com.sun.proxy.$Proxy0(java.reflect.InvocationHandler),它需要一个 InvocationHandler 参数。
-
根据代理Class(Proxy类)的构造器创建对象时,需要传入InvocationHandler。通过构造器传入一个引用,那么必然有个成员变量去接收。没错,代理对象的内部确实有个成员变量invocationHandler,而且代理对象的每个方法内部都会调用handler.invoke()
- invocationHandler的invoke方法根据方法名,调用目标对象对应的原方法,并且支持我们在上下文做增强。
所以,现在的关键先生是 invoke() 这个方法。让我们看看它: invoke()方法的参数有哪些:
- Object proxy:是代理对象本身,而不是目标对象(不要调用,会无限递归,一般不会使用)
- Method method:方法执行器
- Obeject[] args:方法参数
“为什么参数中没有目标对象呢?其实用大腿想想就能明白:JDK要也懵逼呀,你就传给我一个接口,我给你整出这么一堆东西已经仁至义尽,况且一个接口可以被多个类实现,我哪知道你将来要用这个InvocationHandler给哪个目标对象增强呀!所以JDK怎么可能提前预留目标对象的参数类型呢?”
- 好歹有Method代理方法和args参数了,至于目标对象,自己解决试试:
至此,我们自己初步实现了动态代理。回答了上面如何 “增强”,以及JDK如何通过接口实现动态代理的机制。
再进一步,这样我们的增强代码是写死的,如何解耦代理对象与增强代码呢?
-
想一下,我们初步实现的动态代理主要有两步:
- 通过Proxy.getProxyClass(接口类加载器,接口类对象)得到代理类Class对象(有构造器方法)
- 用其构造器方法newInstance(new InvocationHandle.class) 构造代理类对象。增强逻辑通过InvocationHandle实现
-
我们把目标类抽取成参数,把增强代码抽取成,不就可以了吗:
- getLogInvocationHandler(target)的target参数(即目标类对象)是必要的,但getProxt(target, invocationHandler)的target参数是没必要的。因为invocationHandler已经包含目标对象。另外,getProxy()其实只需要知道要实现的接口是什么,就能返回该接口的代理对象
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;
}
};
}
}
很完美了目前。让我们看看官方API:Proxy.newProxyInstance()
目前为止,我们学习都是Proxy.getProxyClass():
- 先获得proxyClazz
- 再根据proxyClazz.getConstructor()获取构造器
- 最后constructor.newInstance()生成代理对象
为了简化代码,我们把这三步封装为getProxy()方法。然而,其实JDK已经提供了一步到位的方法Proxy.newProxyInstance(),你会发现它的参数和我们上面最终封装的getProxy()是一样的
![](https://images.jquan.ink/images/blog-image/blogarticle/clipboard_20220102_122522.png)
```java
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;
}
};
}
}
再看一个例子
public class ProxyTest {
public static void main(String[] args) {
//目标对象
final Target target = new Target();
//增强对象
final Advice advice = new Advice();
//返回值 就是动态生成的代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(), //目标对象类加载器
target.getClass().getInterfaces(), //目标对象相同的接口字节码对象数组
new InvocationHandler() {
//调用代理对象的任何方法 实质执行的都是invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.before(); //前置增强
Object invoke = method.invoke(target, args);//执行目标方法
advice.afterReturning(); //后置增强
return invoke;
}
}
);
//调用代理对象的方法
proxy.save();
}
}
评论
1 / 0