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.
- Command line startup: Java - classpath "X: \ development \ JDK \ lib \ sa-jdi. Jar" sun jvm. hotspot. HSDB
- jps -l view the current java process.
- connect
- Search for class and click generate
- Get class: the generated class file is obtained under the command line path of the first step
- 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");