Java Cglib dynamic agent principle source code analysis

Posted by Chesso on Fri, 28 Jan 2022 05:51:53 +0100

Environment: Java 8

Cglib agent usage

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E://cglib");  
Enhancer enhancer = new Enhancer() ;
enhancer.setSuperclass(PersonDAOImpl.class) ;
enhancer.setCallback(new MethodInterceptor() {
  @Override
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    System.out.println("Before execution...") ;
    Object result = proxy.invokeSuper(obj, args) ;
    System.out.println("After execution...") ;
    return result ;
  }
});
PersonDAOImpl dao = (PersonDAOImpl) enhancer.create() ;
dao.save(new Person()) ;

System.setProperty(
DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E://cglib"); This line of code is used to generate the proxy class generated by cglib and output it to the specified directory

 

Decompile proxy class

The tool is Luyten

Due to the large amount of code, only important codes are posted here

public class PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7 extends PersonDAOImpl 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$save$0$Method;
  private static final MethodProxy CGLIB$save$0$Proxy;
  private static final Object[] CGLIB$emptyArgs;
  private static final Method CGLIB$equals$1$Method;
  private static final MethodProxy CGLIB$equals$1$Proxy;
  private static final Method CGLIB$toString$2$Method;
  private static final MethodProxy CGLIB$toString$2$Proxy;
  private static final Method CGLIB$hashCode$3$Method;
  private static final MethodProxy CGLIB$hashCode$3$Proxy;
  private static final Method CGLIB$clone$4$Method;
  private static final MethodProxy CGLIB$clone$4$Proxy;
    
  static void CGLIB$STATICHOOK1() {
    CGLIB$THREAD_CALLBACKS = new ThreadLocal();
    CGLIB$emptyArgs = new Object[0];
    final Class<?> forName = Class.forName("com.pack.dao.PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7");
    final Class<?> forName2;
    final Method[] methods = ReflectUtils.findMethods(new String[] { "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (forName2 = Class.forName("java.lang.Object")).getDeclaredMethods());
    CGLIB$equals$1$Method = methods[0];
    CGLIB$equals$1$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
    CGLIB$toString$2$Method = methods[1];
    CGLIB$toString$2$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
    CGLIB$hashCode$3$Method = methods[2];
    CGLIB$hashCode$3$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()I", "hashCode", "CGLIB$hashCode$3");
    CGLIB$clone$4$Method = methods[3];
    CGLIB$clone$4$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
    final Class<?> forName3;
    CGLIB$save$0$Method = ReflectUtils.findMethods(new String[] { "save", "(Lcom/pack/anno/config/Person;)V" }, (forName3 = Class.forName("com.pack.dao.PersonDAOImpl")).getDeclaredMethods())[0];
    CGLIB$save$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "(Lcom/pack/anno/config/Person;)V", "save", "CGLIB$save$0");
  }
    
  final void CGLIB$save$0(final Person person) {
    super.save(person);
  }
    
  public final void save(final Person person) {
    MethodInterceptor cglib$CALLBACK_2;
    MethodInterceptor cglib$CALLBACK_0;
    if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
      CGLIB$BIND_CALLBACKS(this);
      cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
    }
    if (cglib$CALLBACK_0 != null) {
      cglib$CALLBACK_2.intercept((Object)this, PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7.CGLIB$save$0$Method, new Object[] { person }, PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7.CGLIB$save$0$Proxy);
      return;
    }
    super.save(person);
  }
    
  public PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7() {
    CGLIB$BIND_CALLBACKS(this);
  }
    
  public static void CGLIB$SET_THREAD_CALLBACKS(final Callback[] array) {
    PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7.CGLIB$THREAD_CALLBACKS.set(array);
  }
  private static final void CGLIB$BIND_CALLBACKS(final Object o) {
    final PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7 personDAOImpl$$EnhancerByCGLIB$$6f07c3f7 = (PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7)o;
    if (!personDAOImpl$$EnhancerByCGLIB$$6f07c3f7.CGLIB$BOUND) {
      personDAOImpl$$EnhancerByCGLIB$$6f07c3f7.CGLIB$BOUND = true;
      Object o2;
      if ((o2 = PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7.CGLIB$THREAD_CALLBACKS.get()) != null || (o2 = PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7.CGLIB$STATIC_CALLBACKS) != null) {
        personDAOImpl$$EnhancerByCGLIB$$6f07c3f7.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])o2)[0];
      }
    }
  }

  static {
    CGLIB$STATICHOOK1();
  }
}

The generated proxy class inherits PersonDAOImpl; Several methods in PersonDAOImpl will generate two corresponding methods. One is to directly call the parent object method CGLIB$save cglib $save $0,, and the other is the save method, which is called through Callback

When executing the save method, first judge cglib $callback_ Whether 0 is empty. If it is empty, the initialization method will be executed

CGLIB$BIND_CALLBACKS(this);

In this method, from cglib $thread_ Get the Callback method (Callback) from the callbacks object (ThreadLocal). How does the ThreadLocal object set the value?

Set CGLIB$THREAD_CALLBACKS value

Executing enhancer The cglib $set of the proxy class PersonDAOImpl$$EnhancerByCGLIB$f07c3f7 is called by reflection when the create() method is used_ THREAD_ Callbacks method

When creating a proxy class:

public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {
  setThreadCallbacks(callbacks);
  try {
    // Explicit reference equality is added here just in case Arrays.equals does not have one
    if (primaryConstructorArgTypes == argumentTypes ||
        Arrays.equals(primaryConstructorArgTypes, argumentTypes)) {
      // If we have relevant Constructor instance at hand, just call it
      // This skips "get constructors" machinery
      return ReflectUtils.newInstance(primaryConstructor, arguments);
    }
    // Take a slow path if observing unexpected argument types
    return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments);
  } finally {
    // clear thread callbacks to allow them to be gc'd
    setThreadCallbacks(null);
  }

}

setThreadCallbacks(callbacks); This line of code is used to execute cglib $set in the proxy class_ THREAD_ Callbacks method.

private void setThreadCallbacks(Callback[] callbacks) {
  try {
    setThreadCallbacks.invoke(generatedClass, (Object) callbacks);
  } catch (IllegalAccessException e) {
    throw new CodeGenerationException(e);
  } catch (InvocationTargetException e) {
    throw new CodeGenerationException(e.getTargetException());
  }
}

 

The MethodInterceptor in this agent class is set. Next, the following code of the save method in the agent class will be executed:

cglib$CALLBACK_2.intercept((Object)this, PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7.CGLIB$save$0$Method, new Object[] { person }, PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7.CGLIB$save$0$Proxy);

Execution of intercept method in MethodInterceptor

intercept method

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  System.out.println("Before execution...") ;
  Object result = proxy.invokeSuper(obj, args) ;
  System.out.println("After execution...") ;
  return result ;
}

Where does the MethodProxy proxy object come from? This needs to go back to the following line of code in the static code segment in the proxy class:

CGLIB$save$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "(Lcom/pack/anno/config/Person;)V", "save", "CGLIB$save$0");

Enter methodproxy Create method:

public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
  MethodProxy proxy = new MethodProxy();
  proxy.sig1 = new Signature(name1, desc);
  proxy.sig2 = new Signature(name2, desc);
  proxy.createInfo = new CreateInfo(c1, c2);
  return proxy;
}

Enter the invokeSuper method

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
  try {
    init();
    FastClassInfo fci = fastClassInfo;
    return fci.f2.invoke(fci.i2, obj, args);
  } catch (InvocationTargetException e) {
    throw e.getTargetException();
  }
}

The init method is used to initialize a fastClassInfo object

private void init()
{
  if (fastClassInfo == null)
  {
    synchronized (initLock)
    {
      if (fastClassInfo == null)
      {
        CreateInfo ci = createInfo;

        FastClassInfo fci = new FastClassInfo();
        fci.f1 = helper(ci, ci.c1);
        fci.f2 = helper(ci, ci.c2);
        fci.i1 = fci.f1.getIndex(sig1);
        fci.i2 = fci.f2.getIndex(sig2);
        fastClassInfo = fci;
        createInfo = null;
      }
    }
  }
}
// FastClassInfo
private static class FastClassInfo {
  FastClass f1;
  FastClass f2;
  int i1;
  int i2;
}

f1 corresponds to c1 in CreateInfo, f2 corresponds to c2 in CreateInfo; Here, we will combine the static code snippets in the proxy class to see who is executing respectively

final Class<?> forName = Class.forName("com.pack.dao.PersonDAOImpl$$EnhancerByCGLIB$$6f07c3f7");
final Class<?> forName3;
CGLIB$save$0$Method = ReflectUtils.findMethods(new String[] { "save", "(Lcom/pack/anno/config/Person;)V" }, (forName3 = Class.forName("com.pack.dao.PersonDAOImpl")).getDeclaredMethods())[0];
CGLIB$save$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "(Lcom/pack/anno/config/Person;)V", "save", "CGLIB$save$0");

Combined with the above code is very clear.

f1 represents the fast access class of the target class and f2 represents the fast access class of the proxy class; i1 represents the index corresponding to the target class method, and i2 represents the index corresponding to the proxy class method.

The quick access class inherits the FastClass class class. Its purpose is to call and execute quickly, because the reflection performance is not high.

The principle is to generate an index value for each method by signing the information (method name, parameter) of each method, and then calling the target method directly according to the index value when calling the method.

Quick access class of target class

 

Continue back to invokeSuper method execution

FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);

At this time, the complete information of fci object is as follows:

 

fci.f2.invoke(fci.i2, obj, args); This line of code is the code that actually executes the target class. Note that i2=19 here

Go to persondaoimpl $$enhancerbycglib $$6f07c3f7 $$fastclassbycglib $$8343b7a5 Class to view the invoke method

 

CGLIB$saveCglib $save $0 method in PersonDAOImpl$$EnhancerByCGLIB$f07c3f7 class

 

Directly call the save method in the parent class PersonDAOImpl.

Here you can understand the three class es generated by cglib.

 

complete!!!

Give me a follow + forward, thank you

Public: Springboot combat case collection

Spring Cloud link tracking zipkin and integrated Elasticsearch storage

Springboot integrates the MyBatis parameter value transfer method

Springboot integrates MyBatis complex query application

Spring cloud Nacos service consumers

Implementation of Springboot interface idempotency based on token

The Springboot project is deployed using docker

How many methods can you use to verify interface parameters in Springboot?

SpringBoot is an operation to improve N-fold performance

Spring boot integrates Quartz to realize task scheduling

Detailed explanation of Actuator of Springboot

SpringCloud zuul dynamic gateway configuration

Spring MVC embedded Tomcat zero configuration

Spring MVC request principle and source code analysis

Spring MVC custom annotations implement interface calls

Detailed steps of integrating ELK log collection with Springboot

Some practical tips in the development of SpringBoot

Topics: Java jvm Spring Boot Distribution Spring Cloud