CGLIB Dynamic Proxy

Summary

CGLIB is a powerful and high performance code generation library. It is widely used in AOP frameworks (Spring, dynaop) to provide methods for intercepting operations. Hibernate, as a popular ORM framework, also uses CGLIB to proxy one-side (many-to-one and one-to-one) associations (another mechanism for delayed collection extraction). CGLIB is an open source project whose code is hosted in github.
CGLIB proxy mainly introduces indirect levels to objects by manipulating byte codes to control object access. We know that there is a dynamic proxy in Java to do this, so why don't we use the Java dynamic proxy directly, but use CGLIB instead? The answer is that CGLIB is more powerful than JDK dynamic proxy. Although JDK dynamic proxy is simple to use, it has a fatal drawback that it can only proxy interfaces. If the class to be proxied is a normal class without an interface, then the Java dynamic proxy cannot be used.

CGLIB Organization Structure


The CGLIB base uses ASM, a short, compact byte code manipulation framework, to manipulate byte codes to generate new classes. In addition to the CGLIB library, scripting languages such as Groovy What BeanShell use ASM to generate byte codes. ASM uses SAX-like parsers for high performance. We do not encourage direct use of ASM because it requires a good understanding of the format of Java byte codes.

CGLIB dynamic proxy example

To use CGLIB dynamic proxy, cglib-x.x.x.jar must be introduced, while ASM is used to manipulate byte codes at the bottom of CGLIB, so asm-x.x.jar must also be introduced. (cglib-2.2.2.jar and asm-3.1.jar are used here)

Maven dependent coordinate is (maven will introduce cglib's transfer dependency)

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

Proxy class:

public class HelloService {

	public String hello(String name) {
		return "hello," + name;
	}

	public String hi(String msg) {
		return "hi,"+msg;
	}

}

Method Interceptor:

public class HelloServiceInterceptor implements MethodInterceptor {
	
	private Enhancer enhancer = new Enhancer();
	
	public Object getProxy(Class<?> clazz) {
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(this);
		return enhancer.create();
	}

	@Override
	public Object intercept(Object arg0, Method arg1, Object[] arg2,
			MethodProxy arg3) throws Throwable {
		System.out.println("Dynamic Proxy Preprocessing...");
		System.out.println("Intercepted methods:" + arg1.getName());
		Object result = arg3.invokeSuper(arg0, arg2);
		System.out.println(result);
		System.out.println("Dynamic proxy postprocessing...");
		return result;
	}

}

Test:

public class CglibDynamicProxy {

	public static void main(String[] args) {
		HelloService proxy = (HelloService)(new HelloServiceInterceptor().getProxy(HelloService.class));
		proxy.hello("Zhang San");
		proxy.hi("cglib Dynamic Proxy");
	}

}

Output:

Dynamic Proxy Preprocessing...
Intercepted methods: hello
hello,Zhang San
 Dynamic proxy postprocessing...
Dynamic Proxy Preprocessing...
Intercepted methods: hi
hi,cglib Dynamic Proxy
 Dynamic proxy postprocessing...

The JDK proxy has strong limitations that require the class being proxied to implement an interface. CGLIB dynamic proxies do not have such mandatory requirements. Simply put, CGLIB causes the generated proxy class to inherit the proxy class and enforce the proxy methods (pre-processing, post-processing, etc.) in the proxy class. At the bottom of CGLIB, ASM is a very powerful Java byte code generation framework.

Analysis

Compared with JDK dynamic proxy, CGLIB implements proxy for general classes without implementing interfaces. In the example above, the proxy class for the target class Target is generated by the following steps:

  1. Create an Enhancer instance;
  2. Set the target class through the setSuperclass method;
  3. Set the intercept object through the setCallback method;
  4. The create method generates a proxy class for Target and returns an instance of the proxy class.

Enhancer is probably the most common class in CGLIB, and Java1.3 The Proxy class introduced in the dynamic proxy is similar. Unlike Proxy, Enhancer can proxy both generic classes and interfaces. Enhancer creates a subclass of the proxied object and intercepts all method calls (including toString and hashCode methods inherited from the Object). Enhancer cannot intercept final methods, such as Object.getClass() method, which is determined by the Java final method semantics. In the same way, Enhancer cannot proxy the fianl class. This is why Hibernate cannot persist the final class.

Proxy class analysis

CGLIB dynamic proxy does not generate proxy-related Class files by default, you can set parameters
-Dcglib.debugLocation=H:\EclipseWorkspace\proxy-test\bin\cglib
or
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "H:\EclipseWorkspace\proxy-test\bin\cglib");
To generate a proxy Class class file.

A little bit about the call chain generated by the proxy class here:

  1. Enhancer : create()
  2. Enhancer : createHelper()
  3. AbstractClassGenerator : create(Object key)
  4. DefaultGeneratorStrategy : generate(ClassGenerator cg)
  5. Enhancer : generateClass(ClassVisitor v)

The final Enhancer Class generateClass(ClassVisitor v) method generates the proxy Class. As far as generating the proxy Class class file is in the generate(ClassGenerator cg) method of the DefaultGeneratorStrategy Class:

return transform(cw.toByteArray());

cw.toByteArray() calls the toByteArray() method of the DebuggingClassWriter class:

public byte[] toByteArray() {
    
  return (byte[]) java.security.AccessController.doPrivileged(
    new java.security.PrivilegedAction() {
        public Object run() {
            
            
            byte[] b = DebuggingClassWriter.super.toByteArray();
            if (debugLocation != null) {
                String dirs = className.replace('.', File.separatorChar);
                try {
                    new File(debugLocation + File.separatorChar + dirs).getParentFile().mkdirs();
                    
                    File file = new File(new File(debugLocation), dirs + ".class");
                    OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
                    try {
                        out.write(b);
                    } finally {
                        out.close();
                    }
......

You can see when debugLocation is in the above method!= When null, the Class byte array b is written to the debugLocation directory, where the debugLocation is initialized in such a static code block:

public static final String DEBUG_LOCATION_PROPERTY = "cglib.debugLocation";

static {
    debugLocation = System.getProperty(DEBUG_LOCATION_PROPERTY);
    if (debugLocation != null) {
        System.err.println("CGLIB debugging enabled, writing to '" + debugLocation + "'");
        try {
            Class.forName("org.objectweb.asm.util.TraceClassVisitor");
            traceEnabled = true;
        } catch (Throwable ignore) {
        }
    }
}

So we can set the parameter cglib.debugLocation controls the proxy class generation path.

Decompilation Agent Class HelloService E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIBdf0ac471

package cglib;

import cglib.HelloService;
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 HelloService$$EnhancerByCGLIB$$df0ac471 extends HelloService implements Factory {

   private boolean CGLIB$BOUND;
   private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
   private static final Callback[] CGLIB$STATIC_CALLBACKS;
   private MethodInterceptor CGLIB$CALLBACK_0;
   private static final Method CGLIB$hello$0$Method;
   private static final MethodProxy CGLIB$hello$0$Proxy;
   private static final Object[] CGLIB$emptyArgs;
   private static final Method CGLIB$hi$1$Method;
   private static final MethodProxy CGLIB$hi$1$Proxy;
   private static final Method CGLIB$finalize$2$Method;
   private static final MethodProxy CGLIB$finalize$2$Proxy;
   private static final Method CGLIB$equals$3$Method;
   private static final MethodProxy CGLIB$equals$3$Proxy;
   private static final Method CGLIB$toString$4$Method;
   private static final MethodProxy CGLIB$toString$4$Proxy;
   private static final Method CGLIB$hashCode$5$Method;
   private static final MethodProxy CGLIB$hashCode$5$Proxy;
   private static final Method CGLIB$clone$6$Method;
   private static final MethodProxy CGLIB$clone$6$Proxy;


   static void CGLIB$STATICHOOK1() {
      CGLIB$THREAD_CALLBACKS = new ThreadLocal();
      CGLIB$emptyArgs = new Object[0];
      Class var0 = Class.forName("cglib.HelloService$$EnhancerByCGLIB$$df0ac471");
      Class var1;
      Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
      CGLIB$finalize$2$Method = var10000[0];
      CGLIB$finalize$2$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$2");
      CGLIB$equals$3$Method = var10000[1];
      CGLIB$equals$3$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$3");
      CGLIB$toString$4$Method = var10000[2];
      CGLIB$toString$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$4");
      CGLIB$hashCode$5$Method = var10000[3];
      CGLIB$hashCode$5$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$5");
      CGLIB$clone$6$Method = var10000[4];
      CGLIB$clone$6$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$6");
      var10000 = ReflectUtils.findMethods(new String[]{"hello", "(Ljava/lang/String;)Ljava/lang/String;", "hi", "(Ljava/lang/String;)Ljava/lang/String;"}, (var1 = Class.forName("cglib.HelloService")).getDeclaredMethods());
      CGLIB$hello$0$Method = var10000[0];
      CGLIB$hello$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)Ljava/lang/String;", "hello", "CGLIB$hello$0");
      CGLIB$hi$1$Method = var10000[1];
      CGLIB$hi$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)Ljava/lang/String;", "hi", "CGLIB$hi$1");
   }

   final String CGLIB$hello$0(String var1) {
      return super.hello(var1);
   }

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

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

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

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

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

   final void CGLIB$finalize$2() throws Throwable {
      super.finalize();
   }

   protected final void finalize() throws Throwable {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }

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

   final boolean CGLIB$equals$3(Object var1) {
      return super.equals(var1);
   }

   public final boolean equals(Object var1) {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }

      if(var10000 != null) {
         Object var2 = var10000.intercept(this, CGLIB$equals$3$Method, new Object[]{var1}, CGLIB$equals$3$Proxy);
         return var2 == null?false:((Boolean)var2).booleanValue();
      } else {
         return super.equals(var1);
      }
   }

   final String CGLIB$toString$4() {
      return super.toString();
   }

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

      return var10000 != null?(String)var10000.intercept(this, CGLIB$toString$4$Method, CGLIB$emptyArgs, CGLIB$toString$4$Proxy):super.toString();
   }

   final int CGLIB$hashCode$5() {
      return super.hashCode();
   }

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

      if(var10000 != null) {
         Object var1 = var10000.intercept(this, CGLIB$hashCode$5$Method, CGLIB$emptyArgs, CGLIB$hashCode$5$Proxy);
         return var1 == null?0:((Number)var1).intValue();
      } else {
         return super.hashCode();
      }
   }

   final Object CGLIB$clone$6() throws CloneNotSupportedException {
      return super.clone();
   }

   protected final Object clone() throws CloneNotSupportedException {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }

      return var10000 != null?var10000.intercept(this, CGLIB$clone$6$Method, CGLIB$emptyArgs, CGLIB$clone$6$Proxy):super.clone();
   }

   public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
      String var10000 = var0.toString();
      switch(var10000.hashCode()) {
      case -1574182249:
         if(var10000.equals("finalize()V")) {
            return CGLIB$finalize$2$Proxy;
         }
         break;
      case -1090657086:
         if(var10000.equals("hi(Ljava/lang/String;)Ljava/lang/String;")) {
            return CGLIB$hi$1$Proxy;
         }
         break;
      case -508378822:
         if(var10000.equals("clone()Ljava/lang/Object;")) {
            return CGLIB$clone$6$Proxy;
         }
         break;
      case 848333779:
         if(var10000.equals("hello(Ljava/lang/String;)Ljava/lang/String;")) {
            return CGLIB$hello$0$Proxy;
         }
         break;
      case 1826985398:
         if(var10000.equals("equals(Ljava/lang/Object;)Z")) {
            return CGLIB$equals$3$Proxy;
         }
         break;
      case 1913648695:
         if(var10000.equals("toString()Ljava/lang/String;")) {
            return CGLIB$toString$4$Proxy;
         }
         break;
      case 1984935277:
         if(var10000.equals("hashCode()I")) {
            return CGLIB$hashCode$5$Proxy;
         }
      }

      return null;
   }

   public HelloService$$EnhancerByCGLIB$$df0ac471() {
      CGLIB$BIND_CALLBACKS(this);
   }

   public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
      CGLIB$THREAD_CALLBACKS.set(var0);
   }

   public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
      CGLIB$STATIC_CALLBACKS = var0;
   }

   private static final void CGLIB$BIND_CALLBACKS(Object var0) {
      HelloService$$EnhancerByCGLIB$$df0ac471 var1 = (HelloService$$EnhancerByCGLIB$$df0ac471)var0;
      if(!var1.CGLIB$BOUND) {
         var1.CGLIB$BOUND = true;
         Object var10000 = CGLIB$THREAD_CALLBACKS.get();
         if(var10000 == null) {
            var10000 = CGLIB$STATIC_CALLBACKS;
            if(CGLIB$STATIC_CALLBACKS == null) {
               return;
            }
         }

         var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
      }

   }

   public Object newInstance(Callback[] var1) {
      CGLIB$SET_THREAD_CALLBACKS(var1);
      HelloService$$EnhancerByCGLIB$$df0ac471 var10000 = new HelloService$$EnhancerByCGLIB$$df0ac471();
      CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
      return var10000;
   }

   public Object newInstance(Callback var1) {
      CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
      HelloService$$EnhancerByCGLIB$$df0ac471 var10000 = new HelloService$$EnhancerByCGLIB$$df0ac471();
      CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
      return var10000;
   }

   public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
      CGLIB$SET_THREAD_CALLBACKS(var3);
      HelloService$$EnhancerByCGLIB$$df0ac471 var10000 = new HelloService$$EnhancerByCGLIB$$df0ac471;
      switch(var1.length) {
      case 0:
         var10000.<init>();
         CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
         return var10000;
      default:
         throw new IllegalArgumentException("Constructor not found");
      }
   }

   public Callback getCallback(int var1) {
      CGLIB$BIND_CALLBACKS(this);
      MethodInterceptor var10000;
      switch(var1) {
      case 0:
         var10000 = this.CGLIB$CALLBACK_0;
         break;
      default:
         var10000 = null;
      }

      return var10000;
   }

   public void setCallback(int var1, Callback var2) {
      switch(var1) {
      case 0:
         this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
      default:
      }
   }

   public Callback[] getCallbacks() {
      CGLIB$BIND_CALLBACKS(this);
      return new Callback[]{this.CGLIB$CALLBACK_0};
   }

   public void setCallbacks(Callback[] var1) {
      this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
   }

   static {
      CGLIB$STATICHOOK1();
   }
}

You can see the proxy class HelloService E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIBdf0ac471 inherits the target class HelloService and implements the Factory interface.
You can see that CGLIB, in addition to the hello and hi methods of the HelloService class, also proxies the finalize, equals, toString, hashCode, clone five methods of the Object class.
The proxy class generates two methods for each method of the target class, for example, for each non-private method in the target class, and two methods for the proxy class, for example, the Hello method of @Override and the CGLIB$hello method 0 ( C G L I B 0(CGLIB 0(CGLIBhello0 (CGLIBhello$0 is equivalent to the hello method of the target class). Call the method proxy of the target class in the sample code. When hello(), the hello() method in the proxy class is actually called. Next, focus on the hello method in the proxy class to see how to implement the proxy function.

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

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

private static final void CGLIB$BIND_CALLBACKS(Object var0) {
    HelloService$$EnhancerByCGLIB$$df0ac471 var1 = (HelloService$$EnhancerByCGLIB$$df0ac471)var0;
    if(!var1.CGLIB$BOUND) {
        var1.CGLIB$BOUND = true;
        Object var10000 = CGLIB$THREAD_CALLBACKS.get();
        if(var10000 == null) {
            var10000 = CGLIB$STATIC_CALLBACKS;
            if(CGLIB$STATIC_CALLBACKS == null) {
                return;
            }
        }
        
        var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
    }
}

You can see in CGLIB B I N D C A L L B A C K S square method in , from C G L I B BIND_ In the CALLBACKS method, from CGLIB In the BINDC ALLBACKS method, from CGLIBTHREAD_CALLBACKS obtains interceptors, if not, from CGLIB S T A T I C C A L L B A C K S Acquired take , that Are you? Interception section implement yes as What set up Location reach C G L I B STATIC_CALLBACKS gets, then how the interceptor is set to CGLIB STATICC ALLBACKS Get, then how the interceptor is set to CGLIBTHREAD_CALLBACKS or CGLIB$STATIC_ What about in CALLBACKS?
The intercept object in the JDK dynamic proxy is passed in by the constructor when the proxy class is instantiated, and in CGLIB the Enhancer's firstInstance method is called to generate the proxy class instance and set the interceptor.
The call path for firstInstance is:

  1. Enhancer: firstInstance
  2. Enhancer: createUsingReflection
  3. Enhancer: setThreadCallbacks
  4. Enhancer: setCallbacksHelper
  5. HelloService E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIBdf0ac471 : CGLIB$SET_THREAD_CALLBACKS

In step 5, the CGLIB of the proxy class is called S E T T H R E A D C A L L B A C K S come finish become Interception section Yes as Of notes enter . lower Noodles see one lower C G L I B SET_THREAD_CALLBACKS to complete the injection of intercepted objects. Take a look at CGLIB SETT HREADC ALLBACKS completes the injection of intercepted objects. Take a look at CGLIBSET_ THREAD_ Decompilation results of CALLBACKS:

public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
  CGLIB$THREAD_CALLBACKS.set(var0);
}

In CGLIB S E T T H R E A D C A L L B A C K S square method in transfer use Yes C G L I B SET_ THREAD_ CGLIB was called in the CALLBACKS method CGLIBTHREAD_was called in the SETT HREADC ALLBACKS method The set method of CALLBACKS saves intercepted objects in CGLIB B I N D C A L L B A C K S square method in send use Yes C G L I B BIND_ CGLIB is used in the CALLBACKS method CGLIBTHREAD_is used in the BINDC ALLBACKS method The get method of CALLBACKS obtains the intercept object and saves it to CGLIB C A L L B A C K 0 in . this kind , stay transfer use generation reason class Of h e l l o square method time , on can with Acquired take reach I They set up Location Of Interception section Yes as , however after through too v a r 10000. i n t e r c e p t ( t h i s , C G L I B CALLBACK_0. This way, when invoking the hello method of the proxy class, you can get the intercept object we set, and then use var10000.intercept(this, CGLIB) CALLBACK0. This way, when calling the hello method of the proxy class, you can get the intercept object that we set, and then use var10000.intercept(this,CGLIBhello) 0 0 0Method, new Object[]{var1}, CGLIB$hello 0 0 0Proxy) to implement proxy.

Here's what the intercept method parameter means:
@para1 obj: Proxy object itself
@para2 method: intercepted method object
@para3 args: method call input
@para4 proxy: Method proxy object used to call intercepted methods

There is a question here, why not use proxy's invokeSuper method instead of directly reflecting the intercepted method generated by the calling proxy class (CGLIB$g$0)? Another point of FastClass is involved here.

FastClass mechanism

The intercepted object of JDK dynamic proxy calls the intercepted method through the reflection mechanism, so CGLIB uses the FastClass mechanism to invoke the intercepted method. The FastClass mechanism is to index the method of a class and invoke the corresponding method directly through the index. Here is a small example to illustrate it, which is more intuitive. :

public class FastClass1 {

    public void hello(String name) {
        System.out.println("hello, " + name);
    }
    
    public void hi(String msg) {
        System.out.println("hi, " + msg);
    }
}

public class FastClass2 {
    
    public Object invoke(Object obj, int methodIndex, Object[] parameters) {
        FastClass1 target = (FastClass1)obj;
        Object result = null;
        switch (methodIndex) {
        case 1:
            target.hello((String)parameters[0]);
            break;
        case 2:
            target.hi((String)parameters[0]);
            break;
        }
        return result;
    }
    
    public int getIndex(String methodDescriptor) {
        switch (methodDescriptor.hashCode()) {
        case -2084786067:
            return 1;
        case -70025314:
            return 2;
        }
        return -1;
    }
}

public class FastClassTest {
    public static void main(String[] args) {
        FastClass1 fastClass1 = new FastClass1();
        FastClass2 fastClass2 = new FastClass2();
        int helloIndex = fastClass2.getIndex("hello(Ljava/lang/String;)V");//Method name (parameter type;...) Return type
        fastClass2.invoke(fastClass1, helloIndex, new Object[]{"Zhang San"});
        int hiIndex = fastClass2.getIndex("hi(Ljava/lang/String;)V");
        fastClass2.invoke(fastClass1, hiIndex, new Object[]{"cglib Dynamic Proxy"});
    }
}

In the example above, FastClass2 is the Fastclass of FastClass1, and there are two methods getIndex and invoke in FastClass2. Each method of FastClass1 is indexed in the getIndex method and is based on the inclusion parameter (method name + method descriptor) To return the corresponding index. invoke invokes the obj method of the object with parameters as its reference according to the specified index. This avoids reflective calls and improves efficiency.
Proxy class (HelloService) E n h a n c e r B y C G L I B EnhancerByCGLIB The code associated with generating the Fastclass in EnhancerByCGLIBdf0ac47 is as follows:

Class var0 = Class.forName("cglib.HelloService$$EnhancerByCGLIB$$df0ac471");
var1 = Class.forName("cglib.HelloService");
CGLIB$hello$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)Ljava/lang/String;", "hello", "CGLIB$hello$0");

Take a look at the MethodProxy.create method:

//The five parameters are
//Class c1 Target Class
//Class c2 Proxy Class
//Description of String desc proxy method: (parameter type) Return value type, because there is no method name in the description, so the proxy method description here is consistent with the proxy method description here
//String name1 proxy method name
//String name2 proxy method name
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;
}

MethodProxy. var0 and var1 are analyzed and generated in create, then getIndex is used to get the index of methods hello and CGLIB$hello$0. The specific generation process will be described later, and a key internal class is described here:

private static class FastClassInfo
{
    FastClass f1; //FastClass of Real Class
    FastClass f2; //FastClass of Proxy Class
    int i1; //Index of true method f1
    int i2; //Index of f2 for proxy methods
}

The code for the invokeSuper method in MethodProxy is as follows:

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();
    }
}

private void init()
{
    /* 
     * Using a volatile invariant allows us to initialize the FastClass and
     * method index pairs atomically.
     * 
     * Double-checked locking is safe with volatile in Java 5.  Before 1.5 this 
     * code could allow fastClassInfo to be instantiated more than once, which
     * appears to be benign.
     */
    if (fastClassInfo == null)
    {
        synchronized (initLock)
        {
            if (fastClassInfo == null)
            {
                CreateInfo ci = createInfo;

                FastClassInfo fci = new FastClassInfo();
                //Generate FastClass for Target Class
                fci.f1 = helper(ci, ci.c1);
                //Generate FastClass for Proxy Class
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(sig1);
                fci.i2 = fci.f2.getIndex(sig2);
                fastClassInfo = fci;
                createInfo = null;
            }
        }
    }
}

private static FastClass helper(CreateInfo ci, Class type) {
    FastClass.Generator g = new FastClass.Generator();
    g.setType(type);
    g.setClassLoader(ci.c2.getClassLoader());
    g.setNamingPolicy(ci.namingPolicy);
    g.setStrategy(ci.strategy);
    g.setAttemptLoad(ci.attemptLoad);
    return g.create();
}

You can see that FastClass generates a call chain:

  1. MethodProxy : invokeSuper(Object obj, Object[] args)
  2. MethodProxy : init()
  3. MethodProxy : helper(CreateInfo ci, Class type)
  4. FastClass.Generator : create()

The invokeSuper() method is called only when a proxy class method is called, so FastClass generation uses a lazy-init-like approach to improve performance.

In MethodProxy. FCI was called in invokeSuper (Object, Object[]). F2. Invoke (fci.i2, obj, args) method, which is the invoke method of the proxy class FastClass, let's look at the proxy class FastClass (each FastClass has a Class class generation, we configured Class file generation, decompile):

package cglib;

import cglib.HelloService..EnhancerByCGLIB..df0ac471;
import java.lang.reflect.InvocationTargetException;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.reflect.FastClass;

public class HelloService$$EnhancerByCGLIB$$df0ac471$$FastClassByCGLIB$$81065074 extends FastClass {

   public HelloService$$EnhancerByCGLIB$$df0ac471$$FastClassByCGLIB$$81065074(Class var1) {
      super(var1);
   }

   public int getIndex(Signature var1) {
      String var10000 = var1.toString();
      switch(var10000.hashCode()) {
      case -2071771415:
         if(var10000.equals("CGLIB$clone$6()Ljava/lang/Object;")) {
            return 22;
         }
         break;
      case -2055565910:
         if(var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
            return 13;
         }
         break;
      case -1725733088:
         if(var10000.equals("getClass()Ljava/lang/Class;")) {
            return 26;
         }
         break;
      case -1663710620:
         if(var10000.equals("CGLIB$equals$3(Ljava/lang/Object;)Z")) {
            return 19;
         }
         break;
      case -1457535688:
         if(var10000.equals("CGLIB$STATICHOOK1()V")) {
            return 15;
         }
         break;
      case -1411783143:
         if(var10000.equals("CGLIB$hashCode$5()I")) {
            return 21;
         }
         break;
      case -1408744366:
         if(var10000.equals("CGLIB$hi$1(Ljava/lang/String;)Ljava/lang/String;")) {
            return 17;
         }
         break;
      case -1090657086:
         if(var10000.equals("hi(Ljava/lang/String;)Ljava/lang/String;")) {
            return 7;
         }
         break;
      case -1026001249:
         if(var10000.equals("wait(JI)V")) {
            return 24;
         }
         break;
      case -894172689:
         if(var10000.equals("newInstance(Lnet/sf/cglib/proxy/Callback;)Ljava/lang/Object;")) {
            return 4;
         }
         break;
      case -879968516:
         if(var10000.equals("CGLIB$hello$0(Ljava/lang/String;)Ljava/lang/String;")) {
            return 16;
         }
         break;
      case -623122092:
         if(var10000.equals("CGLIB$findMethodProxy(Lnet/sf/cglib/core/Signature;)Lnet/sf/cglib/proxy/MethodProxy;")) {
            return 14;
         }
         break;
      case -419626537:
         if(var10000.equals("setCallbacks([Lnet/sf/cglib/proxy/Callback;)V")) {
            return 9;
         }
         break;
      case 243996900:
         if(var10000.equals("wait(J)V")) {
            return 25;
         }
         break;
      case 560567118:
         if(var10000.equals("setCallback(ILnet/sf/cglib/proxy/Callback;)V")) {
            return 8;
         }
         break;
      case 811063227:
         if(var10000.equals("newInstance([Ljava/lang/Class;[Ljava/lang/Object;[Lnet/sf/cglib/proxy/Callback;)Ljava/lang/Object;")) {
            return 5;
         }
         break;
      case 848333779:
         if(var10000.equals("hello(Ljava/lang/String;)Ljava/lang/String;")) {
            return 6;
         }
         break;
      case 946854621:
         if(var10000.equals("notifyAll()V")) {
            return 28;
         }
         break;
      case 973717575:
         if(var10000.equals("getCallbacks()[Lnet/sf/cglib/proxy/Callback;")) {
            return 11;
         }
         break;
      case 1116248544:
         if(var10000.equals("wait()V")) {
            return 23;
         }
         break;
      case 1221173700:
         if(var10000.equals("newInstance([Lnet/sf/cglib/proxy/Callback;)Ljava/lang/Object;")) {
            return 3;
         }
         break;
      case 1230699260:
         if(var10000.equals("getCallback(I)Lnet/sf/cglib/proxy/Callback;")) {
            return 10;
         }
         break;
      case 1365107430:
         if(var10000.equals("CGLIB$finalize$2()V")) {
            return 18;
         }
         break;
      case 1584330438:
         if(var10000.equals("CGLIB$SET_STATIC_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
            return 12;
         }
         break;
      case 1729170762:
         if(var10000.equals("CGLIB$toString$4()Ljava/lang/String;")) {
            return 20;
         }
         break;
      case 1826985398:
         if(var10000.equals("equals(Ljava/lang/Object;)Z")) {
            return 0;
         }
         break;
      case 1902039948:
         if(var10000.equals("notify()V")) {
            return 27;
         }
         break;
      case 1913648695:
         if(var10000.equals("toString()Ljava/lang/String;")) {
            return 1;
         }
         break;
      case 1984935277:
         if(var10000.equals("hashCode()I")) {
            return 2;
         }
      }

      return -1;
   }

   public int getIndex(String var1, Class[] var2) {
      switch(var1.hashCode()) {
      case -2083498449:
         if(var1.equals("CGLIB$finalize$2")) {
            switch(var2.length) {
            case 0:
               return 18;
            }
         }
         break;
      case -1776922004:
         if(var1.equals("toString")) {
            switch(var2.length) {
            case 0:
               return 1;
            }
         }
         break;
      case -1295482945:
         if(var1.equals("equals")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("java.lang.Object")) {
                  return 0;
               }
            }
         }
         break;
      case -1053468136:
         if(var1.equals("getCallbacks")) {
            switch(var2.length) {
            case 0:
               return 11;
            }
         }
         break;
      case -1039689911:
         if(var1.equals("notify")) {
            switch(var2.length) {
            case 0:
               return 27;
            }
         }
         break;
      case -124978607:
         if(var1.equals("CGLIB$equals$3")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("java.lang.Object")) {
                  return 19;
               }
            }
         }
         break;
      case -60403779:
         if(var1.equals("CGLIB$SET_STATIC_CALLBACKS")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("[Lnet.sf.cglib.proxy.Callback;")) {
                  return 12;
               }
            }
         }
         break;
      case -29025553:
         if(var1.equals("CGLIB$hashCode$5")) {
            switch(var2.length) {
            case 0:
               return 21;
            }
         }
         break;
      case 3329:
         if(var1.equals("hi")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("java.lang.String")) {
                  return 7;
               }
            }
         }
         break;
      case 3641717:
         if(var1.equals("wait")) {
            switch(var2.length) {
            case 0:
               return 23;
            case 1:
               if(var2[0].getName().equals("long")) {
                  return 25;
               }
               break;
            case 2:
               if(var2[0].getName().equals("long") && var2[1].getName().equals("int")) {
                  return 24;
               }
            }
         }
         break;
      case 85179481:
         if(var1.equals("CGLIB$SET_THREAD_CALLBACKS")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("[Lnet.sf.cglib.proxy.Callback;")) {
                  return 13;
               }
            }
         }
         break;
      case 99162322:
         if(var1.equals("hello")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("java.lang.String")) {
                  return 6;
               }
            }
         }
         break;
      case 147696667:
         if(var1.equals("hashCode")) {
            switch(var2.length) {
            case 0:
               return 2;
            }
         }
         break;
      case 161998109:
         if(var1.equals("CGLIB$STATICHOOK1")) {
            switch(var2.length) {
            case 0:
               return 15;
            }
         }
         break;
      case 495524492:
         if(var1.equals("setCallbacks")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("[Lnet.sf.cglib.proxy.Callback;")) {
                  return 9;
               }
            }
         }
         break;
      case 1154623345:
         if(var1.equals("CGLIB$findMethodProxy")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("net.sf.cglib.core.Signature")) {
                  return 14;
               }
            }
         }
         break;
      case 1543336191:
         if(var1.equals("CGLIB$toString$4")) {
            switch(var2.length) {
            case 0:
               return 20;
            }
         }
         break;
      case 1811874389:
         if(var1.equals("newInstance")) {
            switch(var2.length) {
            case 1:
               String var10001 = var2[0].getName();
               switch(var10001.hashCode()) {
               case -845341380:
                  if(var10001.equals("net.sf.cglib.proxy.Callback")) {
                     return 4;
                  }
                  break;
               case 1730110032:
                  if(var10001.equals("[Lnet.sf.cglib.proxy.Callback;")) {
                     return 3;
                  }
               }
            case 2:
            default:
               break;
            case 3:
               if(var2[0].getName().equals("[Ljava.lang.Class;") && var2[1].getName().equals("[Ljava.lang.Object;") && var2[2].getName().equals("[Lnet.sf.cglib.proxy.Callback;")) {
                  return 5;
               }
            }
         }
         break;
      case 1817099975:
         if(var1.equals("setCallback")) {
            switch(var2.length) {
            case 2:
               if(var2[0].getName().equals("int") && var2[1].getName().equals("net.sf.cglib.proxy.Callback")) {
                  return 8;
               }
            }
         }
         break;
      case 1837078673:
         if(var1.equals("CGLIB$hi$1")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("java.lang.String")) {
                  return 17;
               }
            }
         }
         break;
      case 1891304123:
         if(var1.equals("CGLIB$hello$0")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("java.lang.String")) {
                  return 16;
               }
            }
         }
         break;
      case 1902066072:
         if(var1.equals("notifyAll")) {
            switch(var2.length) {
            case 0:
               return 28;
            }
         }
         break;
      case 1905679803:
         if(var1.equals("getCallback")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("int")) {
                  return 10;
               }
            }
         }
         break;
      case 1950568386:
         if(var1.equals("getClass")) {
            switch(var2.length) {
            case 0:
               return 26;
            }
         }
         break;
      case 1951977612:
         if(var1.equals("CGLIB$clone$6")) {
            switch(var2.length) {
            case 0:
               return 22;
            }
         }
      }

      return -1;
   }

   public int getIndex(Class[] var1) {
      switch(var1.length) {
      case 0:
         return 0;
      default:
         return -1;
      }
   }

   public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
      df0ac471 var10000 = (df0ac471)var2;
      int var10001 = var1;

      try {
         switch(var10001) {
         case 0:
            return new Boolean(var10000.equals(var3[0]));
         case 1:
            return var10000.toString();
         case 2:
            return new Integer(var10000.hashCode());
         case 3:
            return var10000.newInstance((Callback[])var3[0]);
         case 4:
            return var10000.newInstance((Callback)var3[0]);
         case 5:
            return var10000.newInstance((Class[])var3[0], (Object[])var3[1], (Callback[])var3[2]);
         case 6:
            return var10000.hello((String)var3[0]);
         case 7:
            return var10000.hi((String)var3[0]);
         case 8:
            var10000.setCallback(((Number)var3[0]).intValue(), (Callback)var3[1]);
            return null;
         case 9:
            var10000.setCallbacks((Callback[])var3[0]);
            return null;
         case 10:
            return var10000.getCallback(((Number)var3[0]).intValue());
         case 11:
            return var10000.getCallbacks();
         case 12:
            df0ac471.CGLIB$SET_STATIC_CALLBACKS((Callback[])var3[0]);
            return null;
         case 13:
            df0ac471.CGLIB$SET_THREAD_CALLBACKS((Callback[])var3[0]);
            return null;
         case 14:
            return df0ac471.CGLIB$findMethodProxy((Signature)var3[0]);
         case 15:
            df0ac471.CGLIB$STATICHOOK1();
            return null;
         case 16:
            return var10000.CGLIB$hello$0((String)var3[0]);
         case 17:
            return var10000.CGLIB$hi$1((String)var3[0]);
         case 18:
            var10000.CGLIB$finalize$2();
            return null;
         case 19:
            return new Boolean(var10000.CGLIB$equals$3(var3[0]));
         case 20:
            return var10000.CGLIB$toString$4();
         case 21:
            return new Integer(var10000.CGLIB$hashCode$5());
         case 22:
            return var10000.CGLIB$clone$6();
         case 23:
            var10000.wait();
            return null;
         case 24:
            var10000.wait(((Number)var3[0]).longValue(), ((Number)var3[1]).intValue());
            return null;
         case 25:
            var10000.wait(((Number)var3[0]).longValue());
            return null;
         case 26:
            return var10000.getClass();
         case 27:
            var10000.notify();
            return null;
         case 28:
            var10000.notifyAll();
            return null;
         }
      } catch (Throwable var4) {
         throw new InvocationTargetException(var4);
      }

      throw new IllegalArgumentException("Cannot find matching method/constructor");
   }

   public Object newInstance(int var1, Object[] var2) throws InvocationTargetException {
      df0ac471 var10000 = new df0ac471;
      df0ac471 var10001 = var10000;
      int var10002 = var1;

      try {
         switch(var10002) {
         case 0:
            var10001.<init>();
            return var10000;
         }
      } catch (Throwable var3) {
         throw new InvocationTargetException(var3);
      }

      throw new IllegalArgumentException("Cannot find matching method/constructor");
   }

   public int getMaxIndex() {
      return 28;
   }
}

The proxy FastClass class is rich in content but simple in structure and provides the following methods:

public int getIndex(Signature var1);
public int getIndex(String var1, Class[] var2);
public int getIndex(Class[] var1);
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException;
public Object newInstance(int var1, Object[] var2) throws InvocationTargetException;
public int getMaxIndex();

In MethodProxy. In init(), we see the origin of FastClassInfo's i1 and i2 domain values:

fci.i1 = fci.f1.getIndex(sig1); //sig1 stores method information for the target class
fci.i2 = fci.f2.getIndex(sig2);//sig2 stores method information for proxy classes

Take a closer look at the getIndex method of the proxy class FastClass class:

public int getIndex(Signature var1) {
  String var10000 = var1.toString();
  switch(var10000.hashCode()) {
  case -2071771415:
     if(var10000.equals("CGLIB$clone$6()Ljava/lang/Object;")) {
        return 22;
     }
     break;
  case -2055565910:
     if(var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
        return 13;
     }
     break;
  case -1725733088:
     if(var10000.equals("getClass()Ljava/lang/Class;")) {
        return 26;
     }
     break;
  case -1663710620:
     if(var10000.equals("CGLIB$equals$3(Ljava/lang/Object;)Z")) {
        return 19;
     }
     break;
  case -1457535688:
     if(var10000.equals("CGLIB$STATICHOOK1()V")) {
        return 15;
     }
     break;
  case -1411783143:
     if(var10000.equals("CGLIB$hashCode$5()I")) {
        return 21;
     }
     break;
  case -1408744366:
     if(var10000.equals("CGLIB$hi$1(Ljava/lang/String;)Ljava/lang/String;")) {
        return 17;
     }
     break;
  case -1090657086:
     if(var10000.equals("hi(Ljava/lang/String;)Ljava/lang/String;")) {
        return 7;
     }
     break;
  case -1026001249:
     if(var10000.equals("wait(JI)V")) {
        return 24;
     }
     break;
  case -894172689:
     if(var10000.equals("newInstance(Lnet/sf/cglib/proxy/Callback;)Ljava/lang/Object;")) {
        return 4;
     }
     break;
  case -879968516:
     if(var10000.equals("CGLIB$hello$0(Ljava/lang/String;)Ljava/lang/String;")) {
        return 16;
     }
     break;
  case -623122092:
     if(var10000.equals("CGLIB$findMethodProxy(Lnet/sf/cglib/core/Signature;)Lnet/sf/cglib/proxy/MethodProxy;")) {
        return 14;
     }
     break;
  case -419626537:
     if(var10000.equals("setCallbacks([Lnet/sf/cglib/proxy/Callback;)V")) {
        return 9;
     }
     break;
  case 243996900:
     if(var10000.equals("wait(J)V")) {
        return 25;
     }
     break;
  case 560567118:
     if(var10000.equals("setCallback(ILnet/sf/cglib/proxy/Callback;)V")) {
        return 8;
     }
     break;
  case 811063227:
     if(var10000.equals("newInstance([Ljava/lang/Class;[Ljava/lang/Object;[Lnet/sf/cglib/proxy/Callback;)Ljava/lang/Object;")) {
        return 5;
     }
     break;
  case 848333779:
     if(var10000.equals("hello(Ljava/lang/String;)Ljava/lang/String;")) {
        return 6;
     }
     break;
  case 946854621:
     if(var10000.equals("notifyAll()V")) {
        return 28;
     }
     break;
  case 973717575:
     if(var10000.equals("getCallbacks()[Lnet/sf/cglib/proxy/Callback;")) {
        return 11;
     }
     break;
  case 1116248544:
     if(var10000.equals("wait()V")) {
        return 23;
     }
     break;
  case 1221173700:
     if(var10000.equals("newInstance([Lnet/sf/cglib/proxy/Callback;)Ljava/lang/Object;")) {
        return 3;
     }
     break;
  case 1230699260:
     if(var10000.equals("getCallback(I)Lnet/sf/cglib/proxy/Callback;")) {
        return 10;
     }
     break;
  case 1365107430:
     if(var10000.equals("CGLIB$finalize$2()V")) {
        return 18;
     }
     break;
  case 1584330438:
     if(var10000.equals("CGLIB$SET_STATIC_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
        return 12;
     }
     break;
  case 1729170762:
     if(var10000.equals("CGLIB$toString$4()Ljava/lang/String;")) {
        return 20;
     }
     break;
  case 1826985398:
     if(var10000.equals("equals(Ljava/lang/Object;)Z")) {
        return 0;
     }
     break;
  case 1902039948:
     if(var10000.equals("notify()V")) {
        return 27;
     }
     break;
  case 1913648695:
     if(var10000.equals("toString()Ljava/lang/String;")) {
        return 1;
     }
     break;
  case 1984935277:
     if(var10000.equals("hashCode()I")) {
        return 2;
     }
  }

  return -1;
}

Although the method is long, the logic is simple.
Signature's toString method is used to return method descriptions:

//Here is the method description of the proxy class CGLIB$hello$0, CGLIB$hello CGLIB$hello$0 (Ljava/lang/String;) Ljava/lang/String;(Ljava/lang/String;)Ljava/lang/String;
public String toString() {
    return name + desc;
}

You can see that you can proxy the class CGLIB$hello 0 square method by example , g e t I n d e x return return square method Cable lead 16 , That is f c i . i 2 = 16 . because this M e t h o d P r o x y . i n v o k e S u p e r in f c i . f 2. i n v o k e ( f c i . i 2 , o b j , a r g s ) ; That is by H e l l o S e r v i c e For example, getIndex returns method index 16, which is fci.i2=16. So MethodProxy. FCI in invokeSuper. F2. Invoke (fci.i2, obj, args); HelloService For example, getIndex returns method index 16, which is fci.i2=16. So MethodProxy. FCI in invokeSuper. F2. Invoke (fci.i2, obj, args); HelloService E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIB d f 0 a c 471 df0ac471 df0ac471 F a s t C l a s s B y C G L I B FastClassByCGLIB FastClassByCGLIB 81065074. i n v o k e ( 16 , H e l l o S e r v i c e 81065074.invoke(16,HelloService 81065074.invoke(16,HelloService E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIB$df0ac471, ["Zhang San"].

Take a look at the invoke method of the proxy class FastClass:

public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
  df0ac471 var10000 = (df0ac471)var2; //df0ac471 is the proxy class
  int var10001 = var1; //Proxy method CGLIB$hello$0 index 16 for proxy class hello

  try {
     switch(var10001) {
     case 0:
        return new Boolean(var10000.equals(var3[0]));
     case 1:
        return var10000.toString();
     case 2:
        return new Integer(var10000.hashCode());
     case 3:
        return var10000.newInstance((Callback[])var3[0]);
     case 4:
        return var10000.newInstance((Callback)var3[0]);
     case 5:
        return var10000.newInstance((Class[])var3[0], (Object[])var3[1], (Callback[])var3[2]);
     case 6:
        return var10000.hello((String)var3[0]);
     case 7:
        return var10000.hi((String)var3[0]);
     case 8:
        var10000.setCallback(((Number)var3[0]).intValue(), (Callback)var3[1]);
        return null;
     case 9:
        var10000.setCallbacks((Callback[])var3[0]);
        return null;
     case 10:
        return var10000.getCallback(((Number)var3[0]).intValue());
     case 11:
        return var10000.getCallbacks();
     case 12:
        df0ac471.CGLIB$SET_STATIC_CALLBACKS((Callback[])var3[0]);
        return null;
     case 13:
        df0ac471.CGLIB$SET_THREAD_CALLBACKS((Callback[])var3[0]);
        return null;
     case 14:
        return df0ac471.CGLIB$findMethodProxy((Signature)var3[0]);
     case 15:
        df0ac471.CGLIB$STATICHOOK1();
        return null;
     case 16:
        return var10000.CGLIB$hello$0((String)var3[0]); //The CGLIB$hello$0 method of the proxy class was successfully called here.
     case 17:
        return var10000.CGLIB$hi$1((String)var3[0]);
     case 18:
        var10000.CGLIB$finalize$2();
        return null;
     case 19:
        return new Boolean(var10000.CGLIB$equals$3(var3[0]));
     case 20:
        return var10000.CGLIB$toString$4();
     case 21:
        return new Integer(var10000.CGLIB$hashCode$5());
     case 22:
        return var10000.CGLIB$clone$6();
     case 23:
        var10000.wait();
        return null;
     case 24:
        var10000.wait(((Number)var3[0]).longValue(), ((Number)var3[1]).intValue());
        return null;
     case 25:
        var10000.wait(((Number)var3[0]).longValue());
        return null;
     case 26:
        return var10000.getClass();
     case 27:
        var10000.notify();
        return null;
     case 28:
        var10000.notifyAll();
        return null;
     }
  } catch (Throwable var4) {
     throw new InvocationTargetException(var4);
  }

  throw new IllegalArgumentException("Cannot find matching method/constructor");
}

In the proxy class CGLIB$hello$0 method, its parent class, the Hello method of the target class, is called directly:

final String CGLIB$hello$0(String var1) {
  return super.hello(var1);
}

The original target class method has been successfully invoked.

MethodProxy.invoke(Object obj, Object[] args)

We mentioned MethodProxy above. InvokeSuper (Object obj, Object[] args) method. Then MethodProxy. What is invoke (Object obj, Object[] args)?
Let's start with MethodProxy. Implementation of invoke (Object obj, Object[] args):

public Object invoke(Object obj, Object[] args) throws Throwable {
    try {
        init();
        FastClassInfo fci = fastClassInfo;
        return fci.f1.invoke(fci.i1, obj, args);
    } catch (InvocationTargetException e) {
        throw e.getTargetException();
    } catch (IllegalArgumentException e) {
        if (fastClassInfo.i1 < 0)
            throw new IllegalArgumentException("Protected method: " + sig1);
        throw e;
    }
}

You can see and MethodProxy.invokeSuper(Object obj, Object[] args) is similar, fci.f1 refers to the FastClass of the target class, so take a look at the FastClass class of the target class:

package cglib;

import cglib.HelloService;
import java.lang.reflect.InvocationTargetException;
import net.sf.cglib.core.Signature;
import net.sf.cglib.reflect.FastClass;

public class HelloService$$FastClassByCGLIB$$37e7b6d0 extends FastClass {

   public HelloService$$FastClassByCGLIB$$37e7b6d0(Class var1) {
      super(var1);
   }

   public int getIndex(Signature var1) {
      String var10000 = var1.toString();
      switch(var10000.hashCode()) {
      case -1725733088:
         if(var10000.equals("getClass()Ljava/lang/Class;")) {
            return 8;
         }
         break;
      case -1090657086:
         if(var10000.equals("hi(Ljava/lang/String;)Ljava/lang/String;")) {
            return 1;
         }
         break;
      case -1026001249:
         if(var10000.equals("wait(JI)V")) {
            return 3;
         }
         break;
      case 243996900:
         if(var10000.equals("wait(J)V")) {
            return 4;
         }
         break;
      case 848333779:
         if(var10000.equals("hello(Ljava/lang/String;)Ljava/lang/String;")) {
            return 0;
         }
         break;
      case 946854621:
         if(var10000.equals("notifyAll()V")) {
            return 10;
         }
         break;
      case 1116248544:
         if(var10000.equals("wait()V")) {
            return 2;
         }
         break;
      case 1826985398:
         if(var10000.equals("equals(Ljava/lang/Object;)Z")) {
            return 5;
         }
         break;
      case 1902039948:
         if(var10000.equals("notify()V")) {
            return 9;
         }
         break;
      case 1913648695:
         if(var10000.equals("toString()Ljava/lang/String;")) {
            return 6;
         }
         break;
      case 1984935277:
         if(var10000.equals("hashCode()I")) {
            return 7;
         }
      }

      return -1;
   }

   public int getIndex(String var1, Class[] var2) {
      switch(var1.hashCode()) {
      case -1776922004:
         if(var1.equals("toString")) {
            switch(var2.length) {
            case 0:
               return 6;
            }
         }
         break;
      case -1295482945:
         if(var1.equals("equals")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("java.lang.Object")) {
                  return 5;
               }
            }
         }
         break;
      case -1039689911:
         if(var1.equals("notify")) {
            switch(var2.length) {
            case 0:
               return 9;
            }
         }
         break;
      case 3329:
         if(var1.equals("hi")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("java.lang.String")) {
                  return 1;
               }
            }
         }
         break;
      case 3641717:
         if(var1.equals("wait")) {
            switch(var2.length) {
            case 0:
               return 2;
            case 1:
               if(var2[0].getName().equals("long")) {
                  return 4;
               }
               break;
            case 2:
               if(var2[0].getName().equals("long") && var2[1].getName().equals("int")) {
                  return 3;
               }
            }
         }
         break;
      case 99162322:
         if(var1.equals("hello")) {
            switch(var2.length) {
            case 1:
               if(var2[0].getName().equals("java.lang.String")) {
                  return 0;
               }
            }
         }
         break;
      case 147696667:
         if(var1.equals("hashCode")) {
            switch(var2.length) {
            case 0:
               return 7;
            }
         }
         break;
      case 1902066072:
         if(var1.equals("notifyAll")) {
            switch(var2.length) {
            case 0:
               return 10;
            }
         }
         break;
      case 1950568386:
         if(var1.equals("getClass")) {
            switch(var2.length) {
            case 0:
               return 8;
            }
         }
      }

      return -1;
   }

   public int getIndex(Class[] var1) {
      switch(var1.length) {
      case 0:
         return 0;
      default:
         return -1;
      }
   }

   public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
      HelloService var10000 = (HelloService)var2;
      int var10001 = var1;

      try {
         switch(var10001) {
         case 0:
            return var10000.hello((String)var3[0]);
         case 1:
            return var10000.hi((String)var3[0]);
         case 2:
            var10000.wait();
            return null;
         case 3:
            var10000.wait(((Number)var3[0]).longValue(), ((Number)var3[1]).intValue());
            return null;
         case 4:
            var10000.wait(((Number)var3[0]).longValue());
            return null;
         case 5:
            return new Boolean(var10000.equals(var3[0]));
         case 6:
            return var10000.toString();
         case 7:
            return new Integer(var10000.hashCode());
         case 8:
            return var10000.getClass();
         case 9:
            var10000.notify();
            return null;
         case 10:
            var10000.notifyAll();
            return null;
         }
      } catch (Throwable var4) {
         throw new InvocationTargetException(var4);
      }

      throw new IllegalArgumentException("Cannot find matching method/constructor");
   }

   public Object newInstance(int var1, Object[] var2) throws InvocationTargetException {
      HelloService var10000 = new HelloService;
      HelloService var10001 = var10000;
      int var10002 = var1;

      try {
         switch(var10002) {
         case 0:
            var10001.<init>();
            return var10000;
         }
      } catch (Throwable var3) {
         throw new InvocationTargetException(var3);
      }

      throw new IllegalArgumentException("Cannot find matching method/constructor");
   }

   public int getMaxIndex() {
      return 10;
   }
}

Similarly, the FastClass class structure is similar, pull out invoke, and let's take a closer look:

public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
  HelloService var10000 = (HelloService)var2;
  int var10001 = var1;

  try {
     switch(var10001) {
     case 0:
        return var10000.hello((String)var3[0]);
     case 1:
        return var10000.hi((String)var3[0]);
     case 2:
        var10000.wait();
        return null;
     case 3:
        var10000.wait(((Number)var3[0]).longValue(), ((Number)var3[1]).intValue());
        return null;
     case 4:
        var10000.wait(((Number)var3[0]).longValue());
        return null;
     case 5:
        return new Boolean(var10000.equals(var3[0]));
     case 6:
        return var10000.toString();
     case 7:
        return new Integer(var10000.hashCode());
     case 8:
        return var10000.getClass();
     case 9:
        var10000.notify();
        return null;
     case 10:
        var10000.notifyAll();
        return null;
     }
  } catch (Throwable var4) {
     throw new InvocationTargetException(var4);
  }

  throw new IllegalArgumentException("Cannot find matching method/constructor");
}

There will be an infinite loop.
Using the target class hello() method as an example, call the target class FastClass class invoke(0, HelloService) E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIBdf0ac471, ["Zhang San"]), note that var2 is still a proxy class here (interceptor() method parameter of the method interceptor has not changed), so HelloService var10000 = (HelloService)var2; no error occurs, and then matches case 0 to continue calling the hello() method of the proxy class, but the source of this call is that the user calls the hello() method with the proxy class, a dead loop.

Analyzing problems encountered by CGLIB dynamic proxy

Mainly the problem that demo runs into.

Question 1: Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type

There is no need to introduce the asm-xxx.jar package.
The CGLIB underlying layer generates byte code files through the asm framework. maven can introduce delivery dependencies, so as long as it depends on cglib; otherwise, it can only be introduced manually.

Question 2


Jar package conflict between cglib and asm, reducing cglib jar package version appropriately, from cglib-3.2.8.jar to cglib-2.2.jar here.

Question 3

The method interceptor is

public class HelloServiceInterceptor implements MethodInterceptor {
	
	private Enhancer enhancer = new Enhancer();
	
	public Object getProxy(Class<?> clazz) {
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(this);
		return enhancer.create();
	}

	@Override
	public Object intercept(Object arg0, Method arg1, Object[] arg2,
			MethodProxy arg3) throws Throwable {
		System.out.println("Dynamic Proxy Preprocessing...");
		System.out.println("Proxy class:" + arg0);
		System.out.println("Intercepted methods:" + arg1.getName());
		Object result = arg3.invokeSuper(arg0, arg2);
		System.out.println(result);
		System.out.println("Dynamic proxy postprocessing...");
		return result;
	}

}

A dead loop occurs and the stack overflows:

Based on the above analysis, it is easy to tell where the problem is?
System.out.println("proxy class:"+ arg0); There's a problem here, where arg0 is a proxy class, and arg0 is actually called here. The toString() method. Because CGLIB defaults to the following methods of proxying Object classes:

int hashCode();
public boolean equals(Object obj);
protected native Object clone() throws CloneNotSupportedException;
public String toString();
protected void finalize() throws Throwable;

Therefore, invoking the above method in the intercept method of the method interceptor will re-invoke the intercept method back to the method interceptor through the method of the proxy class, causing a dead cycle of stack overflow.

summary

This paper discusses the basic principles of CGLIB dynamic proxy based on a demo of CGLIB dynamic proxy.

  1. The CGLIB dynamic proxy bottom layer dynamically generates proxy classes and their related classes through the ASM byte code manipulation framework.
  2. The CGLIB dynamic proxy generates three classes dynamically, namely, the proxy Class, the FastClass Class of the proxy Class, and the FastClass Class of the target Class.
  • Target Class Name E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIBhashcode Proxy Class
  • Target Class Name F a s t C l a s s B y C G L I B FastClassByCGLIB FastClassByCGLIBhashcode Target Class FastClass Class
  • Target Class Name E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIBhashcode F a s t C l a s s B y C G L I B FastClassByCGLIB FastClassByCGLIBhashcode Proxy Class FastClass Class
  1. By default, CGLIB proxies five methods of the Object class (hashCode, equals, clone, toString, finalize) and non-private methods of the target class (proxy classes call methods of the parent class or target class, apparently privatemethods are not allowed to be called in subclasses), non-final methods (proxy classes override proxy methods, obviously final methods cannot be overridden).
  2. The proxy class inherits the target class, overrides the proxy method, calls the intercept method of the method interceptor in the proxy method, and finally invokes the method corresponding to the proxy method (CGLIB) in the proxy class via FastClass. generation reason square method name Proxy method name The proxy method name x) and the final method of the parent class is called in the method.
  3. CGLIB speeds up method calls through the FastClass mechanism to compensate for the shortage of reflected calls.
    How can the proxy method of a proxy class efficiently access the actual method of the target class?
    The index of the proxy method of the proxy class (hashcode described by the proxy method) directly locates the method corresponding to the proxy method in the proxy class, in which the actual method of the parent and target class is invoked.
  4. The generation of the FastClass class is delayed until the user invokes the proxy method of the proxy class, and by caching, each class (generated for three classes) is guaranteed to be generated only once.
  5. The proxy class Class is in Enhancer. Is generated when create (), and is generated by
    -Dcglib.debugLocation=H:\EclipseWorkspace\proxy-test\bin\cglib
    or
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "H:\EclipseWorkspace\proxy-test\bin\cglib");
    Controls the generation of three Class files.

Reference resources:

Deep understanding of CGLIB dynamic proxy mechanism
CGLIB(Code Generation Library) Details
cglib source analysis (1): caching and KEY
cglib source analysis (2): Class name generation strategy
cglib source code analysis (3): Class generation strategy
Cglib source code analysis (4): principle analysis of cglib dynamic proxy
Proxy 9 cglib demo analysis and methodProxy and Fastclass source code

Keywords: Java Design Pattern source code Dynamic Proxy

Added by LiLaaron on Thu, 09 Dec 2021 19:54:43 +0200