甚么是cglib
单纯而言,cglib 是用以手动聚合全权类的。与 JDK 便携式的静态全权较之,有几点相同:
JDK 静态全权明确要求被全权类与此同时实现某一USB,而 cglib 渗杂明确要求。
在最终目标方式的继续执行速率上,虽然选用了FastClass监督机制,cglib 更慢(以内部空间换天数,前面会说到)。
常用的静态全权有甚么样?
他们碰触较为多的通常是 JDK 静态全权和责任编辑说到的 cglib,这三个C#都是运转时聚合全权类。spring-aop 与此同时选用了这三种C#。
除此之外,除了javassit和aspectJ等服务器端C#, 它既能校对时聚合全权类,也能在运转时聚合全权类。责任编辑不作扩充,钟爱的能科学研究下。
为甚么要用静态全权
为的是让教给的小东西能逐步形成管理体系,他们须要问许多难题,比如,它是用以化解甚么难题的,不必它泡果,它较之其它有甚么样优劣,之类。这儿,他们须要先思索为甚么要选用静态全权。
为的是更快地答疑那个难题,这里透过三个单纯的范例来逐渐表明。
具体而言,我有三个使用者有关的Controller。
class UserController {
public Response create(UserCreateDTO dto){
String id = userService.create(dto);
return Response.of(id);
}
public Response update(UserUpdateDTO dto){
String id = userService.update(dto);
return Response.of(id);
}
public Response delete(UserDeleteDTO dto){
String id = userService.delete(dto);
return Response.of(id);
}
public Response getById(String id){
UserVO user = userService.getById(id);
return Response.of(user);
}
// zzs001······
}
为的是方便监控跟踪,我希望将每个方式的入参、出参、当前登录人之类信息打印出来。单纯的做法是直接在每个方式里嵌入打印日志的代码,如下:
class UserController {
public Response create(UserCreateDTO dto){
// 打印入参日志
// ······
Response response = Response.of(userService.create(dto));
// 打印出参日志
// ······
return response;
}
public Response update(UserUpdateDTO dto){
// 打印入参日志
// ······
Response response = Response.of(userService.update(dto));
// 打印出参日志 // ······
return response;
}
public Response delete(UserDeleteDTO dto){
// 打印入参日志
// ······
Response response = Response.of(userService.delete(dto));
// 打印出参日志
// ······
return response;
}
public Response getById(String id){
// 打印入参日志
// ······
Response response = Response.of(userService.getById(id));
// 打印出参日志
// ······
return response;
}
// zzs001······
}
明显能看出来,这种做法有三个的难题:一是须要手动添加大量重复代码,二是代码耦合度较高。
当然,难题要三个个化解,具体而言,针对第二个难题,我创建了三个UserControllerCommonLogProxy来专门处理请求日志,如下:
class UserControllerCommonLogProxy extends UserController {
public Response create(UserCreateDTO dto){
// 打印入参日志
// ······
Response response = super.create(dto);
// 打印出参日志
// ······ return response;
}
public Response update(UserUpdateDTO dto){
// 打印入参日志
// ······
Response response = super.update(dto);
// 打印出参日志
// ······
return response;
}
public Response delete(UserDeleteDTO dto){
// 打印入参日志
// ······ Response response = super.delete(dto);
// 打印出参日志
// ······
return response;
}
public Response getById(String id){
// 打印入参日志
// ······
Response response = super.getById(id);
// 打印出参日志
// ······
return response;
}
// zzs001······}
上面范例中,我不直接访问UserController,而是透过UserControllerCommonLogProxy来间接访问。其实,这是全权,严格而言属于静态全权,和接下来要讲的静态全权不太一样。
静态全权化解了代码耦合的难题,但这种做法产生了三个新的难题:须要手动创建和维护大量的全权类。我须要为每三个Controller都增加三个Proxy,项目中将会有大量的*Proxy,而且,当UserController增加方式时,须要在对应的Proxy中与此同时实现。
那个时候,他们会想,要是全权类能手动聚合该多好。于是,静态全权就派上用场了。
他们只要定义好全权类的逻辑,静态全权就能帮他们聚合对应的全权类(能在校对时聚合,也能在运转时聚合),而不须要他们手动创建。
所以,他们用静态全权,本质上是为的是更单纯方便地与此同时实现 AOP。
如何选用cglib
还是继续开篇的范例,我须要打印UserController的入参、出参等信息。
工程环境
JDK:1.8.0_231
maven:3.6.3
IDE:Spring Tool Suite 4.6.1.RELEASE
引入依赖
项目类型 Maven Project,打包方式 jar
<!– cglib –>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
<!– junit –>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
定义全权类的逻辑
具体而言,他们须要在某一地方定义好全权类的逻辑,在责任编辑的范例中,全权的逻辑是在方式继续执行前打印入参,方式继续执行后打印出参。他们能透过与此同时实现MethodInterceptorUSB来定义这些逻辑。根据 aop 联盟的标准(能自行了解下),MethodInterceptor属于一种Advice。
须要注意一点,这儿要透过proxy.invokeSuper来调用最终目标类的方式,而不是选用method.invoke,不然会出现栈溢出等难题。如果你非要调用method.invoke,你须要把最终目标类对象作为LogInterceptor的成员属性,在调用method.invoke时将它作为入参,而不是选用MethodInterceptor.intercept的入参 obj,但是,我不推荐你这么做,因为你将无法享受到 cglib 全权类继续执行快的优势(然而还是许多人这么做)。
public class LogInterceptor implements MethodInterceptor {
// 这儿传入的obj是全权类对象,而不是最终目标类对象
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.err.println(“打印” + method.getName() + “方式的入参”);
// 注意,这儿要调用proxy.invokeSuper,而不是method.invoke,不然会出现栈溢出等难题 Object obj2 = proxy.invokeSuper(obj, args);
System.err.println(“打印” + method.getName() + “方式的出参”);
return obj2;
}
}
。
@Test
public void testBase() throws InterruptedException {
// 设置输出全权类到指定路径,便于前面预测
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, “D:/growUp/test”);
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
// 设置哪个类须要全权
enhancer.setSuperclass(UserController.class);
// 设置怎么全权
enhancer.setCallback(new LogInterceptor());
UserController userController = (UserController) enhancer.create();
// 测试全权类
System.out.println(“————-“);
userController.save();
System.out.println(“————-“);
userController.delete();
System.out.println(“————-“);
userController.update();
System.out.println(“————-“);
userController.find();
}
他们也能与此同时设置多个Callback,须要注意的是,设置了多个Callback不是说三个方式能被多个Callback拦截,而是说最终目标类中不同的方式能被相同的Callback拦截。所以,当设置了多个Callback时,cglib 须要知道甚么样方式选用哪个Callback,他们须要额外设置CallbackFilter来指定每个方式选用的是哪个Callback。项目中我也提供了范例。
运转结果
运转上面的测试方式,能看到,他们选用 cglib 很单纯地与此同时实现了全权,不但较好地解耦合,而且减少了大量重复代码。
————-
打印save方式的入参
增加使用者
打印save方式的出参
————-
打印delete方式的入参
删除使用者
打印delete方式的出参
————-
打印update方式的入参
修改使用者
打印update方式的出参
————-
打印find方式的入参
查找使用者
打印find方式的出参
全权类源代码预测
接下来他们来看看 cglib 的源代码。
cglib 如何聚合全权类的源代码就不预测了,钟爱的能自行科学研究(cglib 的源代码可读性还是很强的),他们只要记住两点就行,1. cglib 的全权类会缓存起来,不会重复创建;2. 选用的是 asm 来聚合Class文件。
他们直接来看全权类方式继续执行的源代码。
全权类文件
在上面范例中,他们指定的文件夹下聚合了三个文件,三个全权类文件,三个FastClass文件。
透过 debug 能发现,全权类文件是调用Enhancer.create的时候聚合的,而三个FastClass文件是第一次调用MethodProxy.invokeSuper的时候才聚合。这三个FastClass是用以干嘛的?
全权类的源代码
下面看看全权类文件的源代码(责任编辑选用Luyten作为反校对工具,考虑篇幅难题,这儿仅展示 update 方式)。
在静态代码块继续执行时,会初始化最终目标类 update 方式对应的Method对象,也会初始化全权类 update 方式对应的MethodProxy对象。那个MethodProxy对象非常重要,透过它才能选用FastClass。
除此之外,他们须要注意三个方式,三个是update方式,该方式中会去调用他们定义的MethodInterceptor的intercept方式,另三个是CGLIB$update$0方式,该方式直接调用UserController的update方式。前面他们会发现,update方式饶了一圈回来最终会调用CGLIB$update$0方式。
//聚合类的名字规则是:被全权classname + “$$”+classgeneratorname+”ByCGLIB”+”$$”+key的hashcodepublic class UserController$$EnhancerByCGLIB$$e6f193aa extends UserController implements Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
//他们一开始传入的MethodInterceptor对象 zzs001 private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
//最终目标类的update方式对象 private static final Method CGLIB$update$0$Method;
//全权类的update方式对象
private static final MethodProxy CGLIB$update$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
final Class<?> forName = Class.forName(“cn.zzs.cglib.UserController$$EnhancerByCGLIB$$e6f193aa”);
final Class<?> forName2;
final Method[] methods = ReflectUtils.findMethods(new String[]{“update”, “()V”, “find”, “()V”, “delete”, “()V”, “save”, “()V”},
(forName2 = Class.forName(“cn.zzs.cglib.UserController”)).getDeclaredMethods());
// 初始化最终目标类的update方式对象
CGLIB$update$0$Method = methods[0];
// 初始化全权类update方式对象
CGLIB$update$0$Proxy = MethodProxy.create((Class) forName2, (Class) forName, “()V”, “update”, “CGLIB$update$0”);
}
// 那个方式将直接调用UserController的update方式
final void CGLIB$update$0() {
super.update();
}
public final void update() {
MethodInterceptor cglib$CALLBACK_2;
MethodInterceptor cglib$CALLBACK_0;
if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
CGLIB$BIND_CALLBACKS(this);
cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
}
//通常走这儿,即调用他们传入MethodInterceptor对象的intercept方式 if (cglib$CALLBACK_0 != null) {
cglib$CALLBACK_2.intercept((Object) this, UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Method, UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$emptyArgs, UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Proxy);
return;
}
super.update();
}
}
创建FastClass文件
在MethodProxy.invokeSuper(Object, Object[])方式中,他们会发现,三个FastClass文件是在init方式中聚合的。当然,它也只会创建一次。
他们用到的主要是全权类的FastClass,透过它,他们能直接调用到CGLIB$update$0方式,相当于能直接调用最终目标类的update方式。
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
//初始化,创建了三个FastClass类对象
init();
FastClassInfo fci = fastClassInfo;
// 这儿将直接调用全权类的CGLIB$update$0方式,而不是透过反射调用
// fci.f2:全权类的FastClass对象,fci.i2为CGLIB$update$0方式对应的索引,obj为当前的全权类对象,args为update方式的参数列表 return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
private void init(){
if (fastClassInfo == null){
synchronized (initLock){
if (fastClassInfo == null){
CreateInfo ci = createInfo;
FastClassInfo fci = new FastClassInfo();
// 创建最终目标类的FastClass对象
fci.f1 = helper(ci, ci.c1);
// 创建全权类的FastClass对象
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(sig1);
fci.i2 = fci.f2.getIndex(sig2);
fastClassInfo = fci;
createInfo = null;
}
}
}
}
FastClass的作用
打开全权类的FastClass文件,能看到,透过方式索引他们能匹配到CGLIB$update$0方式,并且直接调用它,而不须要像 JDK 静态全权一样透过反射的方式调用,极大提高了继续执行效率。
//传入参数:
//n:方式索引
//o:全权类实例
//array:方式输入参数
public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException {
final UserController$$EnhancerByCGLIB$$e6f193aa userController$$EnhancerByCGLIB$$e6f193aa = (UserController$$EnhancerByCGLIB$$e6f193aa)o;
try {
switch (n) {
case 0: {
return new Boolean(userController$$EnhancerByCGLIB$$e6f193aa.equals(array[0]));
}
case 1: {
return userController$$EnhancerByCGLIB$$e6f193aa.toString();
}
case 2: {
return new Integer(userController$$EnhancerByCGLIB$$e6f193aa.hashCode());
}
case 3: {
return userController$$EnhancerByCGLIB$$e6f193aa.clone();
}
// ·······
case 24: {
// 透过匹配方式索引,直接调用该方式,那个方式里将直接调用最终目标类的方式
userController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0();
return null;
}
// ·······
}
catch (Throwable t) {
throw new InvocationTargetException(t);
}
throw new IllegalArgumentException(“Cannot find matching method/constructor”);
}
透过上面的预测,他们找到了 cglib 全权类继续执行起来更慢的原因。
结语
以上基本讲完 cglib 的选用和源代码预测。
最后,感谢阅读。
2021-09-26更改
有关源代码请移步:https://github.com/ZhangZiSheng001/cglib-demo责任编辑为原创文章,转载请附上原文出处链接:https://www.cnblogs.com/ZhangZiSheng001/p/11917086.html