Cglib Callback使用、反编译
6种Callback的使用
统一入口
原始类
//提供六种方法来分别测试六种Callback效果
public class UserService {
public String forFixedValue() {
return "self FixedValue";
}
public String forNoOp() {
return "self NoOp";
}
public String forDispatcher() {
return "self Dispatcher";
}
public String forLazyLoader() {
return "self LazyLoader";
}
public String forInvocationHandler() {
return "self InvocationHandler";
}
public String forMethodInterceptor() {
return "self MethodInterceptor";
}
}
测试类
public class Test {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallbacks(new Callback[]{
new MyFixedValue(), NoOp.INSTANCE, new MyDispatcher(),
new MyLazyLoader(), new MyInvocationHandler(), new MyMethodInterceptor()
});
enhancer.setCallbackFilter(getCallBackFilter());
UserService userService = (UserService) enhancer.create();
System.out.println("result:" + userService.forFixedValue());
System.out.println();
System.out.println("result:" + userService.forNoOp());
System.out.println();
System.out.println("result111111:" + userService.forDispatcher());
System.out.println();
System.out.println("result222222:" + userService.forDispatcher());
System.out.println();
System.out.println("result111111:" + userService.forLazyLoader());
System.out.println();
System.out.println("result222222:" + userService.forLazyLoader());
System.out.println();
System.out.println("result:" + userService.forInvocationHandler());
System.out.println();
System.out.println("result:" + userService.forMethodInterceptor());
System.out.println();
}
static CallbackFilter getCallBackFilter() {
return method -> {
if (method.getName().contains("FixedValue")) {
return 0;
} else if (method.getName().contains("NoOp")) {
return 1;
} else if (method.getName().contains("Dispatcher")) {
return 2;
} else if (method.getName().contains("LazyLoader")) {
return 3;
} else if (method.getName().contains("InvocationHandler")) {
return 4;
} else if (method.getName().contains("MethodInterceptor")) {
return 5;
} else {
return 1;
}
};
}
}
这里使用了多种callback,所以对应使用了callbacks+callbackFilter来逐个对应六种callback方式,语法方面不再涉及。
FixedValue
public class MyFixedValue implements FixedValue {
@Override
public Object loadObject() throws Exception {
System.out.println("------exec MyFixedValue--------");
return "Proxy FixedValue";
}
}
//测试类的调用入口:
// UserService userService = (UserService) enhancer.create();
// System.out.println("result:" + userService.forFixedValue());
//对应执行结果
//------exec MyFixedValue--------
//result:Proxy FixedValue
使用FixedValue来作为回调,会直接替换初始类的方法,可以理解为直接替换原先的方法。
由于直接拦截的初始的所有方法,可能会出现类型异常。
对于案例而言:
-
UserService
的forFixedValue
方法不再被调用,而是执行MyFixedValue
内部的loadObject
方法; -
loadObject
的返回值将作为原始方法(即forFixedValue
)的返回值。
上面这两点是针对UserService
的所有方法而言的,比假如调用userService.hashCode
,本来应当返回int类型,但是走了MyFixedValue
的逻辑,就返回了String类型。类型不同,所以报类型异常。
ps:备注
- 在本例中调用
userService.hashcode
是不会报错的,因为使用了CallbackFilter
来对方法进行了CallBack的调用处理,除了UserService
内部自定义的6个方法以外,都会进入到NoOp
的逻辑中(不代理)。 - 当方法如果走的是FixedValue的时候,才可能会出现类型异常。比如只使用了FixedValue作为回调,即:
enhancer.setCallback(new MyFixedValue())
,并且不使用callbackfilter
,此时调用hashcode()
,就会类型异常。
NoOp
走原本的方法调用逻辑,相当于不进行代理(调用的是原方法)。
使用时直接用NoOp.INSTANCE
即可
enhancer.setCallbacks(new Callback[]{
new MyFixedValue(), NoOp.INSTANCE, new MyDispatcher(),
new MyLazyLoader(), new MyInvocationHandler(), new MyMethodInterceptor()
});
Dispatcher
创建一个类来替换初始类,然后使用替换的类的方法,每次都会重复创建。
注意loadObject
放回的类得是初始类类型的。不然转型异常。从loadObject
这个名字也可以猜出来这是用来加载被调用的累的。
public class MyDispatcher implements Dispatcher {
@Override
public Object loadObject() throws Exception {
System.out.println("------exec MyDispatcher--------");
return new UserService();
}
}
// 测试类调用方法
// System.out.println("result111111:" + userService.forDispatcher());
// System.out.println();
// System.out.println("result222222:" + userService.forDispatcher());
// System.out.println();
// 测试类调用结果
//------exec MyDispatcher--------
//result111111:self Dispatcher
//
//------exec MyDispatcher--------
//result222222:self Dispatcher
测试类中调用了两次forDispatcher
,从打印结果可以看出来每次都执行了loadObject
方法,每次都打印了------exec MyDispatcher--------
。
LazyLoader
用法和Dispatcher差不多,不过它是懒加载的。
创建一个类来替换初始类,然后使用替换的类的方法,不同于Dispatcher,只创建一次。
public class MyLazyLoader implements LazyLoader {
@Override
public Object loadObject() throws Exception {
System.out.println("------exec MyLazyLoader--------");
return new UserService();
}
}
// 测试类调用方法
// System.out.println("result111111:" + userService.forLazyLoader());
// System.out.println();
// System.out.println("result222222:" + userService.forLazyLoader());
// System.out.println();
// 执行结果
//------exec MyLazyLoader--------
//result111111:self LazyLoader
//
//result222222:self LazyLoader
打印结果可以看到,只有第一次的时候执行了loadObject
方法,第二次的时候直接用第一次调用得到的new UserService
来进行方法调用了。
InvocationHandler
通过反射的方式进行代理。
需要注意这里调用的是proxy类的父类的实例。使用method.invoke(proxy args)
,会造成死循环。
- 原理是这里的proxy对象实际上初始类的子类(由cglib生成)
- 在这个子类里面最终会调用到
MyInvocationHandler
的逻辑。 - 那么如果反射的时候调用还是这个子类,相当于又走了一遍上面的逻辑,然后就造成了死循环。
public class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------exec MyFixedValue--------");
System.out.println("before invocationHandler");
Object invoke = method.invoke(proxy.getClass().getSuperclass().newInstance(), args);
System.out.println("after invocationHandler");
return invoke;
}
}
MethodInterceptor
使用最多的Callback,可以完成几乎所有功能。
需要注意这里调用的是invokeSuper
方法,使用invoke
方法,会形成死循环,最终造成堆栈溢出。
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before methodInterceptor");
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("after methodInterceptor");
return result;
}
}
反编译解析
对于如何查看cglib动态
代理生成的class类,然后进行反编译,这里提供了两种方式。
使用sa-jdi.jar(HSDB)反编译
这是jdk提供的开发工具之一,可以连接java进行,对一些jvm底层进行调试。
调试时需要java进程暂停,可以debug断点,或者System.in.read
阻塞。
- 命令行启动:
java -classpath "X:\develop\jdk\lib\sa-jdi.jar" sun.jvm.hotspot.HSDB
-
jps -l
查看当前java进程。
- 连接
- 搜索class并点击生成
- 得到class:生成的class文件在执行第一步的命令行的路径下获取
- 反编译:使用jd-gui,或者将该class直接拖入idea里面。
idea本来就有反编译的功能,比如平常debug的时候,如果点到jar包里面的类,就能看见提示Decompiled .class file,bytecode version:52.0(java 8)
使用DebuggingClassWriter输入Class文件
cglib提供的一种方式,程序运行前设置这个变量,就会把动态生成的class放在指定文件夹下。System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E://resultcallback");