Cglib Callback使用、如何进行反编译

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来作为回调,会直接替换初始类的方法,可以理解为直接替换原先的方法。
由于直接拦截的初始的所有方法,可能会出现类型异常
对于案例而言:

  • UserServiceforFixedValue方法不再被调用,而是执行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阻塞。

  1. 命令行启动:java -classpath "X:\develop\jdk\lib\sa-jdi.jar" sun.jvm.hotspot.HSDB
  2. jps -l查看当前java进程。
    Cglib Callback使用、如何进行反编译
  3. 连接
    Cglib Callback使用、如何进行反编译
  4. 搜索class并点击生成
    Cglib Callback使用、如何进行反编译
  5. 得到class:生成的class文件在执行第一步的命令行的路径下获取
    Cglib Callback使用、如何进行反编译
  6. 反编译:使用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");

上一篇:spring内嵌cglib包,这里藏着一个大坑


下一篇:设计模式——代理模式:CGLIB动态代理