神通广大的cglib和基于它实现的动态代理
保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/62/ )。
前面的文章中我们已经介绍了基于反射的动态代理。但是它有一个制约条件,即被代理的类必须要有一个父接口。但是有些类确实没有父接口,对于这些类而言,基于反射的动态代理是不适用的。
本文我们介绍另一种实现动态代理的方式:基于cglib(Code Generation Library,即代码生成库) 的动态代理。
一个类必须要通过类加载过程将类文件加载到JVM后才能使用。那是否能够直接修改JVM中的字节码信息来修改和创建类呢?
答案是可以的,cglib就是基于这个原理工作的。cglib使用字节码处理框架ASM来转换字节码并生成被代理类的子类。然后这个子类就可以作为代理类展开工作。ASM是一个非常底层的框架,除非你对JVM内部结构包括class文件的格式和指令集都很熟悉,否则不要直接使用ASM。
接下来我们通过示例介绍一下如何用cglib实现动态代理。
首先要在项目中引入cglib工具包,以使用Maven为例,在pom文件中增加下面所示的依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.9</version>
</dependency>
被代理类不需要实现任何接口,下面代码给出了一个简单的被代理类。
public class User{
public String sayHello(String name) {
System.out.println("hello " + name);
return "OK";
}
}
接下来我们编写一个实现了org.springframework.cglib.proxy.MethodInterceptor
接口的类,如下所示。
public class ProxyHandler<T> implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before speak");
Object ans = methodProxy.invokeSuper(o, objects);
System.out.println("after speak");
return ans;
}
}
被代理类中的方法被拦截后,会进入该类中intercept
方法。在该类的intercept
方法中,我们在被代理对象的方法执行前后各增加了一句输出语句。
之后我们就可以建立代理对象,实现动态代理操作,该过程如下所示。
public static void main(String[] args) throws Exception {
Enhancer enhancer = new Enhancer();
// 设置enhancer的回调对象
enhancer.setCallback(new ProxyHandler<>());
// 设置enhancer对象的父类
enhancer.setSuperclass(User.class);
// 创建代理对象,实际为User的子类
User user = (User) enhancer.create();
// 通过代理对象调用目标方法
String ans = user.sayHello("易哥");
System.out.println(ans);
}
我们创建了一个代理对象(即变量user对应的对象),然后调用了其中的方法。最终得到了下图所示的运行结果。
可见,在被代理对象的方法执行前后,均输出了代理对象中增加的语句。
cglib是通过给被代理类创建一个子类,从而实现在不改变被代理类的情况下创建代理类的。因此它也有一定的局限性:也就无法为final类创建代理,因为final类没有子类。
MyBatis在进行对象的懒加载时就有使用cglib创建目标对象的代理对象的过程。其具体过程本文不再展开,大家可以参考《通用源码阅读指导书——MyBatis源码详解》一书的“22.3 懒加载功能”章节,该章节对MyBatis的懒加载相关源码进行了详细的介绍,读完会很有收获。
可以访问个人知乎阅读更多文章:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。