How to use and decompile Cglib Callback

Posted by Pha on Fri, 14 Jan 2022 22:39:12 +0100

Use of 6 kinds of Callback

Unified entrance

Primitive class

//Six methods are provided to test the six Callback effects respectively
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";
    }
}

Test class

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;
            }
        };
    }
}

Many kinds of callbacks are used here, so callbacks+callbackFilter are used to correspond to six kinds of callbacks one by one, and the syntax is no longer involved.

FixedValue

public class MyFixedValue implements FixedValue {
    @Override
    public Object loadObject() throws Exception {
        System.out.println("------exec MyFixedValue--------");
        return "Proxy FixedValue";
    }
}
//Call entry of test class:
//	 UserService userService = (UserService) enhancer.create();
// 	 System.out.println("result:" + userService.forFixedValue());
//Corresponding execution result
//------exec MyFixedValue--------
//result:Proxy FixedValue

Using FixedValue as a callback will directly replace the method of the initial class, which can be understood as directly replacing the original method.
Due to the direct interception of all the initial methods, type exceptions may occur.
For the case:

  • The forFixedValue method of UserService is no longer called, but executes the loadObject method inside MyFixedValue;
  • The return value of loadObject will be used as the return value of the original method (that is, forFixedValue).

The above two points are for all methods of UserService, compared with if UserService is called Hashcode should have returned the int type, but following the logic of MyFixedValue, it returned the String type. The types are different, so the report type is abnormal.
ps: remarks

  • In this example, call userService.hashcode will not report an error because it uses the CallbackFilter to call the method. Except for the six methods customized in UserService, they will enter the logic of NoOp (no proxy).
  • Type exceptions may occur only when the method is FixedValue. For example, only FixedValue is used as the callback, that is: enhancer Setcallback (New myfixedvalue()) and do not use callbackfilter. In this case, calling hashcode() will cause type exception.

NoOp

Following the original method call logic is equivalent to not acting (the original method is called).
Use NOOP directly when using Instance is enough

 enhancer.setCallbacks(new Callback[]{
         new MyFixedValue(), NoOp.INSTANCE, new MyDispatcher(),
         new MyLazyLoader(), new MyInvocationHandler(), new MyMethodInterceptor()
 });

Dispatcher

Create a class to replace the initial class, and then use the method of the replaced class, which will be created repeatedly each time.

Note that the class returned by loadObject must be of the initial class type. Otherwise, the transformation is abnormal. You can also guess from the name loadObject that it is used to load the called object.

public class MyDispatcher implements Dispatcher {
    @Override
    public Object loadObject() throws Exception {
        System.out.println("------exec MyDispatcher--------");
        return new UserService();
    }
}
//  Test class call method
//  System.out.println("result111111:" + userService.forDispatcher());
//  System.out.println();
//  System.out.println("result222222:" + userService.forDispatcher());
//  System.out.println();
//  Test class call result
//------exec MyDispatcher--------
//result111111:self Dispatcher
//
//------exec MyDispatcher--------
//result222222:self Dispatcher

The test class calls two times forDispatcher. From the print results, you can see that loadObject is executed every time, and ------exec MyDispatcher-------- is printed every time.

LazyLoader

The usage is similar to Dispatcher, but it is lazy to load.
Create a class to replace the initial class, and then use the method of the replaced class. Unlike Dispatcher, it is created only once.

public class MyLazyLoader implements LazyLoader {
    @Override
    public Object loadObject() throws Exception {
        System.out.println("------exec MyLazyLoader--------");
        return new UserService();
    }
}
// Test class call method
// System.out.println("result111111:" + userService.forLazyLoader());
// System.out.println();
// System.out.println("result222222:" + userService.forLazyLoader());
// System.out.println();
// results of enforcement
//------exec MyLazyLoader--------
//result111111:self LazyLoader
//
//result222222:self LazyLoader

It can be seen from the print results that only the loadObject method is executed the first time, and the new UserService obtained from the first call is directly used for the second time.

InvocationHandler

Proxy by reflection.
Note that the instance of the parent class of the proxy class is called here. Use method Invoke (proxy args) will cause an endless loop.

  • The principle is that the proxy object here is actually a subclass of the initial class (generated by cglib)
  • In this subclass, the logic of MyInvocationHandler will eventually be called.
  • If the subclass is called during reflection, it is equivalent to walking through the above logic again, and then an endless loop is caused.
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

With the most used Callback, almost all functions can be completed.
It should be noted that the invokeSuper method is called here. Using the invoke method will form an endless loop and eventually cause stack overflow.

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;
    }
}

Decompile parsing

For how to view cglib dynamic
The class class generated by the agent is then decompiled. Two methods are provided here.

Use SA JDI Jar (HSDB) decompile

This is one of the development tools provided by jdk. You can connect java to debug some jvm bottom layers.
The java process needs to be suspended during debugging. You can debug breakpoints or system in. Read blocking.

  1. Command line startup: Java - classpath "X: \ development \ JDK \ lib \ sa-jdi. Jar" sun jvm. hotspot. HSDB
  2. jps -l view the current java process.
  3. connect
  4. Search for class and click generate
  5. Get class: the generated class file is obtained under the command line path of the first step
  6. Decompile: use JD GUI or drag the class directly into the idea.
    idea has the function of decompilation. For example, when debug ging, if you click the class in the jar package, you can see the prompt decompiled class file,bytecode version:52.0(java 8)

Use DebuggingClassWriter to import the Class file

cglib provides a way to set this variable before the program runs, and the dynamically generated class will be placed in the specified folder.
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E://resultcallback");

Topics: Java Back-end