携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情
上一节,我们学习了Java代理相关知识静态代理,这一节我们继续学习动态代理。
一、JDK动态代理
重要的类:
java的动态代理,有两个重要的类,一个是InvocationHandler,一个是Proxy,以及InvocationHandler这个接口的invoke方法。
InvocationHandler是每一个动态代理类都必须要实现的接口,我们通过代理对象调用一个方法的时候,该方法就会被转发由InvocationHandler这个接口的invoke方法来调用。 Proxy类,动态的创建一个代理对象的类,它提供了许多方法,我们用的最多的是Proxy.newProxyInstance方法,该方法的作用就是得到一个动态的代理对象。
JDK代理实现:
public class MyInvocationHandler implements InvocationHandler{
private Object object;
public MyInvocationHandler(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyInvocationHandler invoke begin");
System.out.println("proxy: "+ proxy.getClass().getName());
System.out.println("method: "+ method.getName());
for(Object o : args){
System.out.println("arg: "+ o);
}
//通过反射调用 被代理类的方法
method.invoke(object, args);
System.out.println("MyInvocationHandler invoke end");
return null;
}
public static void main(String [] args){
//创建需要被代理的类
Student s = new Student();
//这一句是生成代理类的class文件,前提是你需要在工程根目录下创建com/sun/proxy目录,不然会报找不到路径的io异常
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
//获得加载被代理类的 类加载器
ClassLoader loader = Thread.currentThread().getContextClassLoader();
//指明被代理类实现的接口
Class<?>[] interfaces = s.getClass().getInterfaces();
// 创建被代理类的委托类,之后想要调用被代理类的方法时,都会委托给这个类的invoke(Object proxy, Method method, Object[] args)方法
MyInvocationHandler h = new MyInvocationHandler(s);
//生成代理类
//注意newProxyInstance的三个参数所代表的含义
Person proxy = (Person)Proxy.newProxyInstance(loader, interfaces, h);
//通过代理类调用 被代理类的方法
proxy.sayHello("yujie.wang", 20);
proxy.sayGoodBye(true, 100);
System.out.println("end");
}
}
JDK动态代理特点:
- Proxy对象不需要implements接口;
- Proxy对象的生成利用JDK的Api,在JVM内存中动态的构建Proxy对象。需要使用java.lang.reflect.Proxy类的newProxyInstance方法
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler invocationHandler );
方法,方法参数说明:
ClassLoader loader:指定当前target对象使用类加载器,获取加载器的方法是固定的
Class<?>[] interfaces:target对象实现的接口的类型,使用泛型方式确认类型
InvocationHandler invocationHandler:事件处理,执行target对象的方法时,会触发事件处理器的方法,会把当前执行target对象的方法作为参数传入。
二、CGLIB动态代理
Cglib动态代理是基于Java的继承机制实现的。
Cglib生成的动态代理类ProxyObject继承于目标类 TargetSubject
,并且持有一个类型为MethodInterceptor
接口的实例引用,这个引用需要我们自己实现,这一点和JDK动态代理的InvocationHandler
接口是一样的。那么Cglib是如何实现的?
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
假如target对象只是一个单独的对象,并没有实现任何接口,这时候就会用到Cglib代理(Code Generation Library),即通过构建一个子类对象,从而实现对target对象的代理,因此目标对象不能是final类(报错),且目标对象的方法不能是final或static(不执行代理功能)。
Cglib依赖的jar:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
Cglib代码实现:
首先、目标对象类AdminCglibService.java
package com.lance.proxy.demo.service;
public class AdminCglibService {
public void update() {
System.out.println("修改管理系统数据");
}
public Object find() {
System.out.println("查看管理系统数据");
return new Object();
}
}
代理类AdminServiceCglibProxy.java
public class AdminServiceCglibProxy implements MethodInterceptor {
private Object target;
public AdminServiceCglibProxy(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance() {
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数
en.setCallback(this);
//创建子类代理对象
return en.create();
}
public Object intercept(Object object, Method method, Object[] arg2, MethodProxy proxy) throws Throwable {
System.out.println("判断用户是否有权限进行操作");
Object obj = method.invoke(target);
System.out.println("记录用户执行操作的用户信息、更改内容和时间等");
return obj;
}
}
Cglib代理测试类CglibProxyTest.java
public class CglibProxyTest {
public static void main(String[] args) {
AdminCglibService target = new AdminCglibService();
AdminServiceCglibProxy proxyFactory = new AdminServiceCglibProxy(target);
AdminCglibService proxy = (AdminCglibService)proxyFactory.getProxyInstance();
System.out.println("代理对象:" + proxy.getClass());
Object obj = proxy.find();
System.out.println("find 返回对象:" + obj.getClass());
System.out.println("----------------------------------");
proxy.update();
}
}
三、总结
1、CgLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CgLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CgLib合适,反之,使用JDK方式要更为合适一些。同时,由于CgLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
2、Spring也可以通过<aop:config proxy-target-class="true">强制使用Cglib代理,使用Java字节码编辑类库ASM操作字节码来实现,直接以二进制形式动态地生成类或其他代理类。
3、SpringBoot 2.x 中会默认使用 Cglib来实现。如果需要修改 AOP 的实现,需要通过spring.aop.proxy-target-class这个配置项来修改。
#配置文件中通过spring.aop.proxy-target-class=false来配置使用JDK
spring.aop.proxy-target-class=false
下图是springboot配置类: