cglib

1.什么是cglib

CGLIB(Code Generation Library)是一个开源项目。
是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。Hibernate支持它来实现PO(Persistent Object 持久化对象)字节码的动态生成。

2.何如使用

导入maven jar包

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

简单编写一个UserService的test方法,用来输出hello world,用来修改类中方法的执行前后事件

1
2
3
4
5
6
public class UserService {
public void test(){
System.out.println("hello world");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @author :zhuanglei
* @description:测试代理类
* @date :2022/1/13 15:28
*/
public class TestClass {

public static void main(final String[] args) {
//new Enhancer对象
Enhancer enhancer = new Enhancer();
//加入需要代理的类
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before method run...");
//在执行之前操作与之后操作
Object result = methodProxy.invokeSuper(o, args);
System.out.println("after method run...");
return result;
}
});
UserService userService = (UserService) enhancer.create();
userService.test();
}
}

以上方法可以看到修改了TestClass类中test方法,在输出hello world之前调用Enhancer回调方法的intercept,在执行test前后,输出语句
代码执行结果为

1
2
3
before method run...
hello world
after method run...

3.能否代理接口?

我们知道了cglib可以代理类,那么是否可以代理接口?我们写一个接口,并且创建一个方法去实现接口

1
2
3
4
5
public interface UserService {

public void test();
}

接下来一样的去代理这个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MainInterface {

public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("切面逻辑...");
return null;
}
});
UserService userService = (UserService) enhancer.create();
userService.test();
}
}

输出结果为

1
切面逻辑...

答案是cglib和jdk里面的动态代理相似,可以进行写入。

4.如何实现的?

cglib可以动态产生一个代理对象,重写内部代码,那么查看他的代理对象类新增内容才能搞明白cglib是如何动态代理的。

动态产生了代理对象,证明我们底层的class类已经被cglib动态生成一个修改后的class类,需要查看生成的class类可以在jvm里加入命令

7Q7FxA.png

生成的class文件

7QHGSH.png

代理类最终样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.brew;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

//继承UserService类 与实现Factory
public class UserService$$EnhancerByCGLIB$$5320231d extends UserService 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;
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
private static final Method CGLIB$test$0$Method;
private static final MethodProxy CGLIB$test$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$equals$1$Method;
private static final MethodProxy CGLIB$equals$1$Proxy;
private static final Method CGLIB$toString$2$Method;
private static final MethodProxy CGLIB$toString$2$Proxy;
private static final Method CGLIB$hashCode$3$Method;
private static final MethodProxy CGLIB$hashCode$3$Proxy;
private static final Method CGLIB$clone$4$Method;
private static final MethodProxy CGLIB$clone$4$Proxy;

static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.brew.UserService$$EnhancerByCGLIB$$5320231d");
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$1$Method = var10000[0];
CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
CGLIB$toString$2$Method = var10000[1];
CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
CGLIB$hashCode$3$Method = var10000[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
CGLIB$clone$4$Method = var10000[3];
CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
//UserService类中找到test方法
CGLIB$test$0$Method = ReflectUtils.findMethods(new String[]{"test", "()V"}, (var1 = Class.forName("com.brew.UserService")).getDeclaredMethods())[0];
CGLIB$test$0$Proxy = MethodProxy.create(var1, var0, "()V", "test", "CGLIB$test$0");
}

//执行父类test方法
final void CGLIB$test$0() {
super.test();
}

//重写了父类的test方法
public final void test() {
//拿到方法拦截器
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}

if (var10000 != null) {
//并且执行拦截器的intercept方法
var10000.intercept(this, CGLIB$test$0$Method, CGLIB$emptyArgs, CGLIB$test$0$Proxy);
} else {
super.test();
}
}

//省略....
....
....
....
}

调用原理,cglib产生的代理类,继承了UserService并且重写了test方法,
在调用test前拿到拦截器的callback方法,并且调用拦截器的intercept方法,内部也能看到toString equles等常用方法,代理类所产生的方法,也是根据UserService内部的方法来产生。

如何赋值CallBack

在test方法中有一个==CGLIB$BIND_CALLBACKS==方法 这个方法调用了
==CGLIB$THREAD_CALLBACKS.get()== 并且这个==CGLIB$THREAD_CALLBACKS==
为ThreadLocal方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static final void CGLIB$BIND_CALLBACKS(Object var0) {
UserService$$EnhancerByCGLIB$$5320231d var1 = (UserService$$EnhancerByCGLIB$$5320231d)var0;
if (!var1.CGLIB$BOUND) {
var1.CGLIB$BOUND = true;
Object var10000 = CGLIB$THREAD_CALLBACKS.get();
if (var10000 == null) {
var10000 = CGLIB$STATIC_CALLBACKS;
if (var10000 == null) {
return;
}
}

var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
}

}

而ThreadLocal newInstance内Cglib提供的Factory提供的。
而他们都是通过enhancer.create()方法初始化调用的。

1
2
3
4
5
6
7
private Object createHelper() {
this.preValidate();
Object key = KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter == ALL_ZERO ? null : new WeakCacheKey(this.filter), this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID);
this.currentKey = key;
Object result = super.create(key);
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
protected Object create(Object key) {
try {
ClassLoader loader = this.getClassLoader();
Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> cache = CACHE;
AbstractClassGenerator.ClassLoaderData data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
if (data == null) {
Class var5 = AbstractClassGenerator.class;
synchronized(AbstractClassGenerator.class) {
cache = CACHE;
data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
if (data == null) {
Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> newCache = new WeakHashMap(cache);
data = new AbstractClassGenerator.ClassLoaderData(loader);
newCache.put(loader, data);
CACHE = newCache;
}
}
}

this.key = key;
Object obj = data.get(this, this.getUseCache());
return obj instanceof Class ? this.firstInstance((Class)obj) : this.nextInstance(obj);
} catch (RuntimeException var9) {
throw var9;
} catch (Error var10) {
throw var10;
} catch (Exception var11) {
throw new CodeGenerationException(var11);
}
}

剩下的内容都能在动态编译后的class文件查看调用方法

5.MethodProxy使用

MethodProxy可以代理执行方法,在代码中有一行

1
2
3
4
5
6
7
8
9
10
11
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before method run...");
//在执行之前操作与之后操作
Object result1 = methodProxy.invoke(userSerivce, args); //test()
Object result2 = methodProxy.invokeSuper(o, args); //CGLIB$test$0
System.out.println("after method run...");
return result;
}
});

这两个方法一种是执行动态class文件中的test方法和CGLIB$test$0(分别代表加强与非加强)
注意:invoke方法不能在使用Object o,不然会出现循环调用,堆栈溢出的问题。

JDK动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @author :zhuanglei
* @description:jdk动态代理
* @date :2022/1/14 16:22
*/
public class JDKDemo {
public static void main(String[] args) {
UserService userService = (UserService) Proxy.newProxyInstance(JDKDemo.class.getClassLoader(), new Class[]{UserInterface.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("test");
return null;
}
});
userService.test();
}
}

ASM & javassist

javassist使用全解析

Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。

本地项目使用示例

飞书jar包内使用okhttp的方式请求接口,需要使okhttp兼容ssl方法需要重写
1
2
3
public static OkHttpClient create(long connectionTimeout, long socketTimeout, TimeUnit timeUnit) {
return (new Builder()).connectTimeout(connectionTimeout, timeUnit).readTimeout(socketTimeout, timeUnit).build();
}
使用javassist动态加载可以解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//需要修改jar包的类路径
String clsName = "com.larksuite.oapi.core.api.tools.OKHttps";

//ClassPool是CtClass的对象容器,
//它按需读取类文件来构造 CtClass 对象,并且保存 CtClass 对象以便以后使用。
ClassPool cp = ClassPool.getDefault();

//CtClass对象获取容器内指定类
CtClass cc = cp.get(clsName);

//getDeclaredMethod获取类加载中已声明的方法,这边指定获取OkHttpClient下的create方法
CtMethod m = cc.getDeclaredMethod("create");

//修改create方法内容为cn.anicert.config.MHostnameVerifier内的build方法
m.setBody("{ return cn.anicert.config.MHostnameVerifier.build;}");

//重新将com.larksuite.oapi.core.api.tools.OKHttps类放入CtClass容器
CtClass ctClass = cp.makeClass("com.larksuite.oapi.core.api.tools.OKHttps");


//toClass : 将修改后的CtClass加载至当前线程的上下文类加载器中
//CtClass的toClass方法是通过调用本方法实现。
//需要注意的是一旦调用该方法,则无法继续修改已经被加载的class;
Class<?> aClass = cc.toClass(Thread.currentThread().getContextClassLoader(),null);
//如果不指定类加载器,这个方法会用当前线程下的类加载器去加载类