动态代理
1、什么是动态代理?
前面已经提到了,动态代理就是【在内存】中动态生成【字节码代理类】的技术。(虽然不需要开发者书写,但是在内存层面依然存在该代理对象】
优点:
- 减少了代理类的数量
- 并且解决了代码复用的问题。
动态代理常见的实现技术包括以下三种
- JDK内置的动态代理技术 :只能代理接口
- 位置:java.lang.reflect.Porxy ,是一个注解
- CGLIB(Code Generation Library)动态代理技术,一个开源项目,生成类库,可以适用于接口和类
- 但是CGLIB的低层是通过【继承】实现的(虽然是继承,但是由于是在内存动态生成字节码类,所以并不会增加耦合度),所以性能比JDK动态代理好
- CGLIB的低层还有一个字节码处理框架 【ASM】(可能阅读源码时会遇到)
- Javassist动态代理技术:东京大学的千叶滋教授所创建的开源项目。为JBOOS实现“aop”框架
- mybatis框架底层就是用的javassist创建接口的字节码对象
Spring的低层主要是靠JDK内置的动态代理和CGLIB实现
2、Java内置的动态代理
1)如何使用Proxy
还是模拟静态代理的场景。接下来展示一下如何使用。
// 用法
OrderService target = new OrderServiceImpl();
Object proxyInstance = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler());
- newProxyInstance,翻译就是新建代理对象,本质上该方法做了两件事
- 在内存中动态的生成了一个代理类的字节码class
- "new"对象了,通过内存中生成代理类,实例化了该代理对象
- newProxyInstance() 方法有三个参数,分别的作用进行分析
-
ClassLoader loader
——类加载器,将字节码class文件加载到内存当中。而且JDK要求,目标类的类加载器,必须和代理类的类加载器使用同一个! -
Class<>?[] interfaces
——代理类和目标类实现的共同接口 -
InvocationHandler h
—— 翻译是调用处理器。
-
2)调用处理器InvocationHandler
这个参数的含义,在理解之前可以做一个简单的推测,我们在使用代理对象时,增强功能的代码应该写在哪里?(首先JDK肯定是不知道开发者要写什么代码的) 目前的三个参数已经用掉了两个,所以,不难推测调用处理器的作用。结合老杜的笔记,如下:
调用处理器的作用:写增强代码。同时InvocationHandler的接口,那我们就需要实现并重写该接口,再作为参数传入Proxy
重写后就可以发现,该接口需要重写一个方法——invoke()
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
思考:为什么要强制实现InvocationHandler接口呢?
- 因为一个类实现接口必须实现接口中的方法
- 以下的方法必须是invoke(),因为JDK在低层调用invoke的方法已经写好了
- 也就是说,invoke方法并不是开发者调用,而是为了给JDK调用
思考2:invoke方法什么时候被调用?(如何调用?)
-
首先尝试重写invoke方法
-
调用target(目标对象)—— 还是调用原对象
-
尝试将newProxyInstance的返回值进行转型,向下转型为OrderService,并尝试调用。代码如下
OrderService target = new OrderServiceImpl(); OrderService obj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler()); obj.insert(); obj.modify();
-
发现打印出INVOKE了,但是,目标对象的真正的方法无了,没有实现代理方法
-
3)invoke方法的使用
invoke方法有如下三个参数
- 第一个参数:代理对象的引用
- 第二个参数,目标对象上的目标方法
- 第三个参数,目标方法上的实参
核心思路: invoke方法在执行过程中,使用method方法来调用目标对象的目标方法
那么,如何使用Method来调用目标方法呢?
Method是Java反射的一个类,具体用法需要参照一下API给出的解释。
也就是说方法四要素:哪个对象、哪个方法、传什么参数、传什么值。
而我们这边需要调用目标对象的目标方法,还缺一个关键 —— 目标对象。
那么这里就需要使用构造函数将目标对象传入到该方法内以供调用,经过分得出以下方法,如下。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimerInvocationHandler implements InvocationHandler {
private Object target;
public TimerInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">>>BEFORE INVOKE ....");
long begin = System.currentTimeMillis();
Object retValue = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println(">>>AFTER INVOKE...COST:"+(end-begin));
return null;
}
}
客户端
public class Client {
public static void main(String[] args) {
OrderService target = new OrderServiceImpl();
OrderService obj = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler(target));
obj.insert();
obj.modify();
}
}
输出结果
这里我们增强了普通的无返回值的方法,那么如何代理(增强)带返回值的方法呢?
4)invoke方法的返回值
对OrderService做如下修改,新增了一个带返回值的方法以便测试
-
尝试在上述代码中,不做修改,直接调用代理对象 —— 返回值为空
-
那么在Invoke里 将 方法执行结果return。—— 即可得到预期的结果
注意:这个invoke 方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话:invoke 方法必须将目标对象的目标方法执行结果继续返回。
至此,已经完成了通过JDK内置的代理类,实现使用代理模式增强业务代码的目的了。但是代码其实其实还是有点多,可以尝试使用工具类进行一个封装优化。
5)进一步封装一个自定义的工具方法
这里老杜的封装其实不是很严谨,应该传参还需要设置成可以传入一个自定义的处理器,这样这个工具类才会更加易用。改进后如下
工具类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class ProxyUtil {
/**
* 生成代理对象
* @param target 目标对象
* @param handler 调用处理器
* @return 代理对象
*/
public static Object getProxyInstance(Object target , InvocationHandler handler){
Object proxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
return proxyInstance;
}
}
优化后客户端写法如下:
public class Client {
public static void main(String[] args) {
OrderService target = new OrderServiceImpl();
TimerInvocationHandler timerInvocationHandler = new TimerInvocationHandler(target);
OrderService obj = (OrderService) ProxyUtil.getProxyInstance(
target, timerInvocationHandler);
}
}
3、CGLIB 动态代理
老杜提到,这里的CGLIB写法并不是重点,需要了解一个大概的写法,并且明白,是使用继承的特性实现的动态代理。所以,CGLIB会更加的泛用。
1)什么是CGLIB(和JDK动态代理的区别
- CGLIB既可以代理接口,又可以代理类
- 低层是采用继承的方式是实现的
- 所以被代理的目标类,不能使用final修饰
- 另外。CGLIB功能更强大,效率也更高
2)用法/写法
- 既然提到CGLIB可以继承普通类,那么我们就直接声明一个普通类作为目标对象
- 创建客户端类 Client
- 创建字节码增强对象
Enhancer
,作为CGLIB库中的核心对象,就是依靠它来生存代理类的 - 设置父类(告诉CGLIB父类,即目标类是谁)
- 设置回调(相当于JDK代理中的调用处理器,invocationHandler)
- 创建代理对象:做两件事
- 在内存中生成目标对象的子类,其实就是代理类的字节码
- 创建代理对象(通过代理类
3)回调如何设置?
实现的思路和JDK动态地理类似,都是实现一个接口
- InvocationHandler (方法四要素)
- MethodInterceptor (方法四要素)
不同之处在于,target目标对象不需要再手动创建构造函数传入了。
如果运行报错 ,是因为JDK版本太高了,需要加参数
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/sun.net.util=ALL-UNNAMED
4)测试并观察代理对象的运行结果
-
结果是成功的,实现了业务增强的效果。
-
老杜这里提到,要注意这个打印出来的对象地址是比较特殊的,可以用于以后调试的时候查看
测试代码如下:
业务逻辑:
package com.zhc.cglib.proxy.service;
public class OrderServiceCGlib {
public void modify() {
try {
Thread.sleep(1500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("FINISH MODIFY");
}
public String getName() {
System.out.println("GET NAME ...");
return "ZHANGSAN";
}
}
自定义方法拦截器:文章来源:https://www.toymoban.com/news/detail-759560.html
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TimerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long begin = System.currentTimeMillis();
Object retValue = methodProxy.invokeSuper(target, objects);
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-begin));
return retValue;
}
}
客户端 :文章来源地址https://www.toymoban.com/news/detail-759560.html
package com.zhc.cglib.proxy.client;
import com.zhc.cglib.proxy.service.OrderServiceCGlib;
import com.zhc.cglib.proxy.service.TimerMethodInterceptor;
import net.sf.cglib.proxy.Enhancer;
public class CGlibClient {
public static void main(String[] args) {
/**
* 1、创建增强器
* 2、设置父类
* 3、设置回调
* 4、获取代理对象
*/
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderServiceCGlib.class);
enhancer.setCallback(new TimerMethodInterceptor());
OrderServiceCGlib orderServiceCGlib = (OrderServiceCGlib) enhancer.create();
System.out.println(orderServiceCGlib.getName());
orderServiceCGlib.modify();
System.out.println(orderServiceCGlib);
}
}
到了这里,关于【Java学习笔记】 动态代理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!