Design pattern [3.3] - Interpretation of CGLIB dynamic agent source code

Keywords: Design Pattern

cglib dynamic proxy

Introduction to cglib

CGLIB is an open source project, a powerful high-performance and high-quality code generation library, which can expand Java classes and implement Java interfaces at run time. The bottom layer is to use a small and fast bytecode processing framework ASM to convert bytecode and generate new classes.

Theoretically, we can also use ASM to generate code directly, but we are required to be familiar with the JVM, the class file format, and the instruction set of bytecode.

This thing is not in the JDK package. You need to download and import it yourself or import Maven coordinates.

I select Maven import and add it to the pom.xml file:

<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
</dependencies>

Student.java:

public class Student {
    public void learn() {
        System.out.println("I'm a student. I want to study");
    }
}

Myproxy.java (proxy class)

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

import java.lang.reflect.Method;

public class StudentProxy implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("Before agency -------");
        proxy.invokeSuper(obj, args);
        System.out.println("After agency -------");
        return null;
    }

}

Test class (Test.java)

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

public class Test {
    public static void main(String[] args) {
        // The proxy class file is stored on the local disk for us to decompile and view the source code
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/aphysia/Desktop");

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Student.class);
        enhancer.setCallback(new StudentProxy());
        Student student = (Student) enhancer.create();
        student.learn();

    }
}

After running, the result is:

CGLIB debugging enabled, writing to '/Users/xuwenhao/Desktop'
Before agency -------
I'm a student. I want to study
 After agency -------

In the folder we selected, the proxy class code is generated:

Source code analysis

First, we need to implement the methodinterceptor (method interceptor) interface for the proxy class. This interface has only one method intercept, and the parameters are:

  • obj: object to be enhanced
  • Method: the method to be intercepted
  • args: method parameter to be intercepted
  • proxy: represents the method object to trigger the parent class
package net.sf.cglib.proxy;
import java.lang.reflect.Method;
public interface MethodInterceptor extends Callback {
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;
}

Let's look back to the method enhancer.create() to create the proxy class. This method means: if necessary, generate a new class and use the specified callback (if any) to create a new object instance. Use the parameterless constructor of the superclass.

    public Object create() {
        classOnly = false;
        argumentTypes = null;
        return createHelper();
    }

For the main method logic, we have to look at createHelper(). In addition to verification, we call KEY_FACTORY.newInstance() method generates EnhancerKey object, KEY_FACTORY is a static EnhancerKey interface, and newinstance () is a method in the interface, focusing on super.create(key), which calls the method of the parent class:

    private Object createHelper() {
        // check
        preValidate();
        Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
                ReflectUtils.getNames(interfaces),
                filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
                callbackTypes,
                useFactory,
                interceptDuringConstruction,
                serialVersionUID);
        this.currentKey = key;
        Object result = super.create(key);
        return result;
    }

Abstract classgenerator is the parent class of Enhancer. The main logic of the create(key) method is to obtain class loader, cache to get class load data, and then reflect the construction object. There are two methods to create instance objects:

  • fistInstance(): this method should not be invoked in a regular stream. Technically, {@link #wrapCachedClass(Class)} uses {@link EnhancerFactoryData} as the cache value, which supports faster instantiation than the usual old reflection lookup and invocation. For backward compatibility reasons, this method remains unchanged: only to prevent it from being used. (my understanding is that the current logic will not go to this branch because it is busy, but the case is still saved for compatibility). The internal logic actually uses ReflectUtils.newInstance(type).
  • nextInstance(): the real class that creates the proxy object
    protected Object create(Object key) {
        try {
            ClassLoader loader = getClassLoader();
            Map<ClassLoader, ClassLoaderData> cache = CACHE;
            ClassLoaderData data = cache.get(loader);
            if (data == null) {
                synchronized (AbstractClassGenerator.class) {
                    cache = CACHE;
                    data = cache.get(loader);
                    if (data == null) {
                        Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
                        data = new ClassLoaderData(loader);
                        newCache.put(loader, data);
                        CACHE = newCache;
                    }
                }
            }
            this.key = key;
            Object obj = data.get(this, getUseCache());
            if (obj instanceof Class) {
                return firstInstance((Class) obj);
            }
            // How to actually create objects
            return nextInstance(obj);
        } catch (RuntimeException e) {
            throw e;
        } catch (Error e) {
            throw e;
        } catch (Exception e) {
            throw new CodeGenerationException(e);
        }
    }

This method is defined in AbstractClassGenerator, but it is actually the implementation of calling the subclass Enhancer, mainly by obtaining parameter types, parameters, and callback objects, and using these parameters to generate proxy objects.

    protected Object nextInstance(Object instance) {
        EnhancerFactoryData data = (EnhancerFactoryData) instance;

        if (classOnly) {
            return data.generatedClass;
        }

        Class[] argumentTypes = this.argumentTypes;
        Object[] arguments = this.arguments;
        if (argumentTypes == null) {
            argumentTypes = Constants.EMPTY_CLASS_ARRAY;
            arguments = null;
        }
        // structure
        return data.newInstance(argumentTypes, arguments, callbacks);
    }

The internal implementation logic calls ReflectUtils.newInstance(), and the parameter types are different:

        public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {
            setThreadCallbacks(callbacks);
            try {

                if (primaryConstructorArgTypes == argumentTypes ||
                        Arrays.equals(primaryConstructorArgTypes, argumentTypes)) {
                    return ReflectUtils.newInstance(primaryConstructor, arguments);
                }
               return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments);
            } finally {
            
                setThreadCallbacks(null);
            }

        }

To follow through is to obtain the constructor method, construct the proxy object by reflection, and finally call the method provided by JDK:

    public static Object newInstance(Class type, Class[] parameterTypes, Object[] args) {
        return newInstance(getConstructor(type, parameterTypes), args);
    }
    public static Object newInstance(final Constructor cstruct, final Object[] args) {
            
        boolean flag = cstruct.isAccessible();
        try {
            if (!flag) {
                cstruct.setAccessible(true);
            }
            Object result = cstruct.newInstance(args);
            return result;
        } catch (InstantiationException e) {
            throw new CodeGenerationException(e);
        } catch (IllegalAccessException e) {
            throw new CodeGenerationException(e);
        } catch (InvocationTargetException e) {
            throw new CodeGenerationException(e.getTargetException());
        } finally {
            if (!flag) {
                cstruct.setAccessible(flag);
            }
        }
                
    }

Open the proxy class file automatically generated by it, and you will find that it also generates those methods, plus some enhanced methods:

The generated proxy class inherits the original class:

public class Student$$EnhancerByCGLIB$$929cb5fe extends Student implements Factory {
    ...
}

Look at the generated enhancement method. In fact, it calls the intercept() method. This method is implemented by ourselves earlier, so the enhanced function of the proxy object is completed:

    public final void learn() {
        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$learn$0$Method, CGLIB$emptyArgs, CGLIB$learn$0$Proxy);
        } else {
            super.learn();
        }
    }

What is the difference between cglib and jdk dynamic proxy

  1. jdk dynamic proxy uses interceptors and reflection to generate an anonymous class of proxy interface, which is handed over to InvokeHandler for processing when executing methods. CGLIB dynamic proxy uses ASM framework, modifies the original bytecode, and then generates a new subclass for processing.
  2. The JDK agent needs to implement the interface, but CGLIB is not mandatory.
  3. Before JDK1.6, cglib was more efficient than reflection because it used bytecode generation technology. However, jdk has also been optimized and its efficiency has been improved.

[about the author]:
Qin Huai, official account [Qinhuai grocery store] Author, the road of technology is not at the moment. Although it is slow, it does not stop. My personal writing direction: Java source code analysis, JDBC, Mybatis, Spring, redis, distributed, sword finger Offer, LeetCode, etc. I seriously write every article. I don't like the title party and fancy. I mostly write a series of articles. I can't guarantee that what I write is completely correct, but I promise What you have written has been practiced or searched for information. Please correct any omissions or errors.

Sword finger Offer all questions PDF

What did I write in 2020?

Open source programming notes

Posted by vfwood on Mon, 08 Nov 2021 11:25:49 -0800