Detailed explanation of CGLIB, one of Java Dynamic agents

Keywords: Java JDK Spring Hibernate

In the last article< Java agent pattern and dynamic agent >In Java, we introduce the static proxy mode and dynamic proxy mode, and take JDK native dynamic proxy as an example to explain. In this article, we will introduce the dynamic agent based on CGLIB and compare it with the native dynamic agent.

CGLIB introduction

CGLIB(Code Generation Library) is an open source, high-performance and high-quality code generation library (code generation package).

It can extend Java classes and implement Java interfaces at runtime. Hibernate uses it to realize the dynamic generation of PO(Persistent Object persistent object) bytecode, and Spring AOP uses it to provide the interception of methods.

The bottom layer of CGLIB is to transform bytecode and generate new classes by using ASM, a small and fast bytecode processing framework. However, we are not encouraged to use ASM framework directly, because of the high requirements for the underlying technology.

Using examples

First, we introduce the dependency of CGLIB:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

Here we take UserDao, which operates user data, as an example, to enhance its function through dynamic agent (adding logs before and after execution). UserDao is defined as follows:

public class UserDao {

	public void findAllUsers(){
		System.out.println("UserDao Query all users");
	}

	public String findUsernameById(int id){
		System.out.println("UserDao according to ID Query users");
		return "Official account: new horizon of procedure";
	}
}

Create an interceptor to implement the interface net.sf.cglib.proxy.MethodInterceptor, which is used for intercepting callback of methods.

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author sec
 * @version 1.0
 * @date 2020/3/24 8:14 morning
 **/
public class LogInterceptor implements MethodInterceptor {

	/**
	 *
	 * @param obj Represents the object to be enhanced
	 * @param method Means to intercept
	 * @param objects Array represents the parameter list. The basic data type needs to be passed in its packing type, such as int -- > integer, long long, double -- > double
	 * @param methodProxy Represents a proxy for a method, and invokesouper represents a call to a method of the object being proxied
	 * @return results of enforcement
	 * @throws Throwable abnormal
	 */
	@Override
	public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
		before(method.getName());
		// Note that invoke seuper is called instead of invoking, otherwise, the loop is dead;
		// Methodproxy.invokeseuper executes the methods of the original class;
		// method.invoke implements the method of subclass;
		Object result = methodProxy.invokeSuper(obj, objects);
		after(method.getName());
		return result;
	}

	/**
	 * Execute before invoking the invoke method
	 */
	private void before(String methodName) {
		System.out.println("Calling method" + methodName +"Log processing before");
	}

	/**
	 * Execute after invoking the invoke method
	 */
	private void after(String methodName) {
		System.out.println("Calling method" + methodName +"Log processing after");
	}
}

The intercept method that implements the MethodInterceptor interface. Parameters in this method:

  • obj: indicates the object to be enhanced;
  • Method: indicates the method to be intercepted;
  • objects: indicates the parameters of the method to be intercepted;
  • methodProxy: indicates the method object to trigger the parent class.

Methodproxy.invokeseuper, which is mainly called inside the method, executes the method of the original class. If the invoke method is called, a dead loop will occur.

An example of using the client is as follows:

import net.sf.cglib.proxy.Enhancer;

public class CglibTest {

	public static void main(String[] args) {

		// The process of obtaining proxy object by CGLIB dynamic proxy
		// Create Enhancer object, similar to the Proxy class of JDK dynamic Proxy
		Enhancer enhancer = new Enhancer();
		// Set the bytecode file of the target class
		enhancer.setSuperclass(UserDao.class);
		// Set callback function
		enhancer.setCallback(new LogInterceptor());
		// The create method formally creates a proxy class
		UserDao userDao = (UserDao) enhancer.create();
		// Calling specific business methods of proxy class
		userDao.findAllUsers();
		userDao.findUsernameById(1);
	}
}

Execute the main method of the client to print the result as follows:

Log processing before calling findAllUsers
 UserDao query all users
 Log processing after calling findAllUsers
 Log processing before calling findUsernameById
 UserDao query users by ID
 Log processing after calling findUsernameById

As you can see, the corresponding "enhancement processing" has been added before and after our method.

Decompile class

In the first line of the main method, we can also add the following settings to store the class file of the agent class.

// The class file of agent class is stored in the local disk, which can be decompiled to view the source code
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zzs/temp");

Executing the program again, we can see that three class files are generated in the corresponding directory:

UserDao$$EnhancerByCGLIB$$1169c462.class
UserDao$$EnhancerByCGLIB$$1169c462$$FastClassByCGLIB$$22cae79c.class
UserDao$$FastClassByCGLIB$$197ae7fa.class

Some decompilations are as follows:

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class UserDao$$EnhancerByCGLIB$$1169c462 extends UserDao implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$findAllUsers$0$Method;
    private static final MethodProxy CGLIB$findAllUsers$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$findUsernameById$1$Method;
    private static final MethodProxy CGLIB$findUsernameById$1$Proxy;
    private static final Method CGLIB$equals$2$Method;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final Method CGLIB$toString$3$Method;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final Method CGLIB$hashCode$4$Method;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final Method CGLIB$clone$5$Method;
    private static final MethodProxy CGLIB$clone$5$Proxy;
    
    public final void findAllUsers() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$findAllUsers$0$Method, CGLIB$emptyArgs, CGLIB$findAllUsers$0$Proxy);
        } else {
            super.findAllUsers();
        }
    }

    final String CGLIB$findUsernameById$1(int var1) {
        return super.findUsernameById(var1);
    }

    public final String findUsernameById(int var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$findUsernameById$1$Method, new Object[]{new Integer(var1)}, CGLIB$findUsernameById$1$Proxy) : super.findUsernameById(var1);
    }
    
    // ...
}

From the decompile source code, we can see that the proxy object inherits the UserDao, the interceptor calls the intercept() method, and the intercept() method is implemented by the custom LogInterceptor, so we finally call the intercept() method in LogInterceptor to complete the dynamic proxy realization from the proxy object to the target object.

CGLIB creating dynamic proxy class procedure

(1) Find the method definitions of all non final public types on the target class;

(2) Convert the qualified method definition to bytecode;

(3) Convert the bytecode into the class object of the corresponding agent;

(4) Implements the MethodInterceptor interface, which is used to process requests for all methods on the proxy class.

Comparison between JDK dynamic agent and CGLIB

JDK dynamic proxy: Based on Java reflection mechanism, the proxy object can be generated only when the business class of the interface is implemented.

CGLIB dynamic proxy: it is implemented based on ASM mechanism, and the subclass of business class is generated as proxy class.

Advantages of JDK Proxy:

With minimal dependency, simple code implementation, simplified development and maintenance, JDK native support, it is more reliable than CGLIB, and smoothly upgraded with JDK version. The bytecode class library usually needs to be updated to ensure that it can be used in the new version of Java.

Based on the advantages of CGLIB:

There is no need to implement the interface to achieve a proxy class that is non intrusive and only operates the concerned classes without increasing the workload for other related classes. High performance.

Summary

There are so many about the implementation of dynamic agents. In specific business scenarios, how to choose can be compared according to their advantages and disadvantages. However, as a benchmark project in Java field Spring, many functions are implemented by CGLIB.


New vision of program: wonderful and growing [new WeChat vision] - https://img2020.cnblogs.com/other/1742867/202003/1742867-20200324091908506-1175698480.png official account.

Posted by Tekron-X on Mon, 23 Mar 2020 18:43:56 -0700