Introduction and principle of cglib -- CallBackFilter and delayed loading

Keywords: Java

Original text: Introduction and principle of CGLIB (through inherited dynamic proxy)

 

1, What is CGLIB?

CGLIB is a powerful and high-performance code generation package. It provides a proxy for classes that do not implement interfaces, and provides a good supplement to the dynamic proxy of JDK. You can usually use Java's dynamic proxy to create a proxy, but CGLIB is a good choice when the class to proxy does not implement an interface or for better performance.

2, CGLIB principle

CGLIB principle: dynamically generate a subclass of the class to be proxied, and the subclass overrides all non final methods of the class to be proxied. In the subclass, the method interception technology is used to intercept the calls of all parent methods, and the crosscutting logic is weaved into it. It is faster than JDK dynamic proxy using java reflection.

CGLIB bottom layer: use bytecode processing framework ASM to convert bytecode and generate new classes. Direct use of ASM is discouraged because it requires you to be familiar with the internal structure of the JVM, including the format of class files and instruction sets.

CGLIB disadvantage: for the final method, proxy cannot be performed.

3, Application of CGLIB

It is widely used by many AOP frameworks, such as Spring AOP and dynaop. Hibernate uses CGLIB to proxy single ended (many to one and one to one) associations.

4, CGLIB API

1. Jar package:

cglib-nodep-2.2.jar: using nodep package does not need to be associated with asm jar package, which contains asm classes

cglib-2.2.jar: this jar package needs to be associated with the asm jar package, otherwise an error will be reported when running

2. CGLIB class library:

Because there is little basic code, it is difficult to learn, mainly due to the lack of documents and examples, which is also a deficiency of CGLIB.

The CGLIB version used in this series is 2.2.

net.sf.cglib.core: bottom bytecode processing classes, most of which are related to ASM.

net.sf.cglib.transform: conversion of compile time or run-time classes and class files

net.sf.cglib.proxy: a class that implements the creation of proxies and method interceptors

net.sf.cglib.reflect: a class that implements fast reflection and C# style proxy

net.sf.cglib.util: tool classes such as collection sorting

net.sf.cglib.beans:JavaBean related tool classes

This article introduces the implementation of a dynamic agent through MethodInterceptor and Enhancer.

1, First, let's talk about the dynamic agent in JDK:

The dynamic Proxy in JDK is implemented through the reflection class Proxy and InvocationHandler callback interface,

However, the class for dynamic proxy in JDK must implement an interface, that is, it can only proxy the methods defined in the interface implemented by this class, which has certain limitations in actual programming, and the efficiency of using reflection is not very high.

2, Implemented using CGLib:

Using CGLib to realize dynamic proxy is not limited by the interface that proxy classes must implement. Moreover, the bottom layer of CGLib adopts ASM bytecode generation framework and bytecode technology to generate proxy classes, which is more efficient than using Java reflection. The only thing to note is that CGLib cannot proxy a method declared as final, because the principle of CGLib is to dynamically generate subclasses of the proxy class.

Next, we will introduce the implementation of dynamic proxy using CGLib through an example.

1. Proxy class:

First, define a class that does not implement any interfaces

 1 package com.zghw.cglib;  
 2   
 3 /** 
 4  * No interface is implemented, and the target class of CGlib dynamic proxy is required 
 5  *  
 6  * @author zghw 
 7  * 
 8  */  
 9 public class TargetObject {  
10     public String method1(String paramName) {  
11         return paramName;  
12     }  
13   
14     public int method2(int count) {  
15         return count;  
16     }  
17   
18     public int method3(int count) {  
19         return count;  
20     }  
21   
22     @Override  
23     public String toString() {  
24         return "TargetObject []"+ getClass();  
25     }  
26 }</span>  

2,Interceptor:

Define an interceptor. When calling the target method, CGLib will call back the MethodInterceptor interface method interception to implement your own proxy logic, similar to the InvocationHandler interface in JDK.

 1 package com.zghw.cglib;  
 2 
 3 import java.lang.reflect.Method;  
 4 import net.sf.cglib.proxy.MethodInterceptor;  
 5 import net.sf.cglib.proxy.MethodProxy;  
 6 /** 
 7  * Target object interceptor to implement MethodInterceptor 
 8  * @author zghw 
 9  * 
10  */  
11 public class TargetInterceptor implements MethodInterceptor{  
12 /** 
13      * Rewrite method interception to add business before and after method 
14      * Object obj Target object 
15      * Method method Target method 
16      * Object[] params Is a parameter, 
17      * MethodProxy proxy CGlib Method proxy object 
18      */  
19 @Override  
20 public Object intercept(Object obj, Method method, Object[] params,  
21             MethodProxy proxy) throws Throwable {  
22         System.out.println("Before calling");  
23         Object result = proxy.invokeSuper(obj, params);  
24         System.out.println(" After call"+result);  
25 return result;  
26     }  
27 }  

Parameters: Object is the proxy class instance dynamically generated by CGLib, Method is the proxy Method reference called by the entity class above, Object [] is the parameter value list, and MethodProxy is the proxy reference of the generated proxy class to the Method.

Return: the value returned from the method call of the proxy instance.

Where proxy.invokeSuper(obj,arg):

Call the parent method of the proxy method on the proxy class instance (that is, the corresponding method in the entity class TargetObject)

In this example, only one sentence is printed before and after calling the proxy class method. Of course, other complex logic can be used in actual programming.

3. Generate dynamic proxy class:

 1 package com.zghw.cglib;  
 2   
 3 import net.sf.cglib.proxy.Callback;  
 4 import net.sf.cglib.proxy.CallbackFilter;  
 5 import net.sf.cglib.proxy.Enhancer;  
 6 import net.sf.cglib.proxy.NoOp;  
 7   
 8 public class TestCglib {  
 9     public static void main(String args[]) {  
10         Enhancer enhancer =new Enhancer();  
11         enhancer.setSuperclass(TargetObject.class);  
12         enhancer.setCallback(new TargetInterceptor());  
13         TargetObject targetObject2=(TargetObject)enhancer.create();  
14         System.out.println(targetObject2);  
15         System.out.println(targetObject2.method1("mmm1"));  
16         System.out.println(targetObject2.method2(100));  
17         System.out.println(targetObject2.method3(200));  
18     }  
19 }  

here Enhancer Class is CGLib A bytecode enhancer in, which can easily extend the class you want to deal with, and you will often see it in the future.

First, set the proxy class TargetObject as the parent class, then set the interceptor TargetInterceptor, and finally execute enhancer.create() to dynamically generate a proxy class and forcibly transform it from Object to parent type TargetObject.

Finally, the method is called on the proxy class

4. Callback filtercallbackfilter

1, Function

During CGLib callback, you can set different callback logic for different methods, or do not execute callback at all.

There is no similar function in JDK dynamic proxy. Calling InvocationHandler interface method is valid for all methods in proxy class.

 

Define the class that implements the filter CallbackFilter interface:

 1 package com.zghw.cglib;  
 2 
 3 import java.lang.reflect.Method;  
 4 import net.sf.cglib.proxy.CallbackFilter;  
 5 /** 
 6  * Callback method filtering 
 7  * @author zghw 
 8  * 
 9  */  
10 public class TargetMethodCallbackFilter implements CallbackFilter {  
11 /** 
12      * Filtering method 
13      * The returned value is a number, which represents the index position in the Callback array and the Callback to be used 
14      */  
15 @Override  
16 public int accept(Method method) {  
17 if(method.getName().equals("method1")){  
18             System.out.println("filter method1 ==0");  
19 return 0;  
20         }  
21 if(method.getName().equals("method2")){  
22             System.out.println("filter method2 ==1");  
23 return 1;  
24         }  
25 if(method.getName().equals("method3")){  
26             System.out.println("filter method3 ==2");  
27 return 2;  
28         }  
29 return 0;  
30     }  
31 }  

Where the return value is the position index of each method of the proxied class in the Callback array Callback [] (see below).

 
 1 package com.zghw.cglib;  
 2   
 3 import net.sf.cglib.proxy.Callback;  
 4 import net.sf.cglib.proxy.CallbackFilter;  
 5 import net.sf.cglib.proxy.Enhancer;  
 6 import net.sf.cglib.proxy.NoOp;  
 7   
 8 public class TestCglib {  
 9     public static void main(String args[]) {  
10         Enhancer enhancer =new Enhancer();  
11         enhancer.setSuperclass(TargetObject.class);  
12         CallbackFilter callbackFilter = new TargetMethodCallbackFilter();  
13           
14         /** 
15          * (1)callback1: method interceptors  
16            (2)NoOp.INSTANCE: This NoOp means no operator, that is, do nothing. The proxy class directly calls the proxy method without interception. 
17            (3)FixedValue: Indicates the return value of the locking method. No matter what value is returned by the method of the proxy class, the callback method returns a fixed value. 
18          */  
19         Callback noopCb=NoOp.INSTANCE;  
20         Callback callback1=new TargetInterceptor();  
21         Callback fixedValue=new TargetResultFixed();  
22         Callback[] cbarray=new Callback[]{callback1,noopCb,fixedValue};  
23         //enhancer.setCallback(new TargetInterceptor());  
24         enhancer.setCallbacks(cbarray);  
25         enhancer.setCallbackFilter(callbackFilter);  
26         TargetObject targetObject2=(TargetObject)enhancer.create();  
27         System.out.println(targetObject2);  
28         System.out.println(targetObject2.method1("mmm1"));  
29         System.out.println(targetObject2.method2(100));  
30         System.out.println(targetObject2.method3(100));  
31         System.out.println(targetObject2.method3(200));  
32     }  
33 }  

 

 1 package com.zghw.cglib;  
 2   
 3 import net.sf.cglib.proxy.FixedValue;  
 4 /**  
 5  * Indicates the return value of the locking method. No matter what value is returned by the method of the proxy class, the callback method returns a fixed value.  
 6  * @author zghw  
 7  *  
 8  */  
 9 public class TargetResultFixed implements FixedValue{  
10   
11     /**  
12      * This class implements the FixedValue interface and locks the callback value to 999  
13      * (Integer. The method using FixedValue callback defined in CallbackFilter is getConcreteMethodFixedValue, and the return value of this method is integer).  
14      */  
15     @Override  
16     public Object loadObject() throws Exception {  
17         System.out.println("Lock result");  
18         Object obj = 999;  
19         return obj;  
20     }  
21   
22 }  

5.Delay loading objects

1, Function:
When it comes to delayed loading, you should often contact it, especially when using Hibernate. This article will analyze the implementation of delayed loading through an example.
LazyLoader interface inherits Callback, so it is also a Callback type in CGLib.

Another deferred load interface Dispatcher.

The Dispatcher interface also inherits from Callback and is also a Callback type.

However, the difference between Dispatcher and LazyLoader is that LazyLoader only triggers the proxy class callback method when accessing the deferred load attribute for the first time, while Dispatcher will trigger the proxy class callback method every time accessing the deferred load attribute.

 

2, Example:
First, define an entity class LoaderBean, which has a property PropertyBean that needs to be loaded late.  

 1 package com.zghw.cglib;  
 2   
 3 import net.sf.cglib.proxy.Enhancer;  
 4   
 5 public class LazyBean {  
 6     private String name;  
 7     private int age;  
 8     private PropertyBean propertyBean;  
 9     private PropertyBean propertyBeanDispatcher;  
10   
11     public LazyBean(String name, int age) {  
12         System.out.println("lazy bean init");  
13         this.name = name;  
14         this.age = age;  
15         this.propertyBean = createPropertyBean();  
16         this.propertyBeanDispatcher = createPropertyBeanDispatcher();  
17     }  
18   
19       
20   
21     /** 
22      * Only the first lazy load 
23      * @return 
24      */  
25     private PropertyBean createPropertyBean() {  
26         /** 
27          * Use cglib for lazy loading to add a proxy for the object that needs to be loaded late. When obtaining the object properties, first initialize the object through the proxy class callback method. 
28          * When the object does not need to be loaded, the object will not be initialized as long as the attribute in the object is not obtained (in the implementation of CGLib, just access the getter method of the attribute in the object, 
29          * The proxy class callback is automatically triggered). 
30          */  
31         Enhancer enhancer = new Enhancer();  
32         enhancer.setSuperclass(PropertyBean.class);  
33         PropertyBean pb = (PropertyBean) enhancer.create(PropertyBean.class,  
34                 new ConcreteClassLazyLoader());  
35         return pb;  
36     }  
37     /** 
38      * Lazy loading every time 
39      * @return 
40      */  
41     private PropertyBean createPropertyBeanDispatcher() {  
42         Enhancer enhancer = new Enhancer();  
43         enhancer.setSuperclass(PropertyBean.class);  
44         PropertyBean pb = (PropertyBean) enhancer.create(PropertyBean.class,  
45                 new ConcreteClassDispatcher());  
46         return pb;  
47     }  
48     public String getName() {  
49         return name;  
50     }  
51   
52     public void setName(String name) {  
53         this.name = name;  
54     }  
55   
56     public int getAge() {  
57         return age;  
58     }  
59   
60     public void setAge(int age) {  
61         this.age = age;  
62     }  
63   
64     public PropertyBean getPropertyBean() {  
65         return propertyBean;  
66     }  
67   
68     public void setPropertyBean(PropertyBean propertyBean) {  
69         this.propertyBean = propertyBean;  
70     }  
71   
72     public PropertyBean getPropertyBeanDispatcher() {  
73         return propertyBeanDispatcher;  
74     }  
75   
76     public void setPropertyBeanDispatcher(PropertyBean propertyBeanDispatcher) {  
77         this.propertyBeanDispatcher = propertyBeanDispatcher;  
78     }  
79   
80     @Override  
81     public String toString() {  
82         return "LazyBean [name=" + name + ", age=" + age + ", propertyBean="  
83                 + propertyBean + "]";  
84     }  
85 }  
 1 package com.zghw.cglib;  
 2   
 3 public class PropertyBean {  
 4     private String key;  
 5     private Object value;  
 6     public String getKey() {  
 7         return key;  
 8     }  
 9     public void setKey(String key) {  
10         this.key = key;  
11     }  
12     public Object getValue() {  
13         return value;  
14     }  
15     public void setValue(Object value) {  
16         this.value = value;  
17     }  
18     @Override  
19     public String toString() {  
20         return "PropertyBean [key=" + key + ", value=" + value + "]" +getClass();  
21     }  
22       
23 }  

 

 1 package com.zghw.cglib;  
 2   
 3 import net.sf.cglib.proxy.LazyLoader;  
 4   
 5 public class ConcreteClassLazyLoader implements LazyLoader {  
 6     /** 
 7      * Add a proxy to the object that needs to be loaded late. When obtaining the object properties, first initialize the object through the proxy class callback method. 
 8      * When the object does not need to be loaded, the object will not be initialized as long as the attribute in the object is not obtained (in the implementation of CGLib, just access the getter method of the attribute in the object, 
 9      * The proxy class callback is automatically triggered). 
10      */  
11     @Override  
12     public Object loadObject() throws Exception {  
13         System.out.println("before lazyLoader...");  
14         PropertyBean propertyBean = new PropertyBean();  
15         propertyBean.setKey("zghw");  
16         propertyBean.setValue(new TargetObject());  
17         System.out.println("after lazyLoader...");  
18         return propertyBean;  
19     }  
20   
21 }  

 

 1 package com.zghw.cglib;  
 2   
 3 import net.sf.cglib.proxy.Dispatcher;  
 4   
 5 public class ConcreteClassDispatcher implements Dispatcher{  
 6   
 7     @Override  
 8     public Object loadObject() throws Exception {  
 9         System.out.println("before Dispatcher...");  
10         PropertyBean propertyBean = new PropertyBean();  
11         propertyBean.setKey("xxx");  
12         propertyBean.setValue(new TargetObject());  
13         System.out.println("after Dispatcher...");  
14         return propertyBean;  
15     }  
16   
17 }  

 

6. Interface generator InterfaceMaker

1, Function:

InterfaceMaker dynamically generates an interface that contains all the methods defined by the specified class.

2, Example:

Posted by ikscovski on Mon, 01 Nov 2021 20:20:07 -0700