Spring Source Analysis 5: JDK and cglib Dynamic Agent Principles

AOP is based on Java dynamic proxy. Understanding and using two kinds of dynamic proxy can help us understand AOP better. Before explaining AOP, let's first look at how Java dynamic proxy is used and the underlying implementation principles.

From https://www.jianshu.com/u/668d0795a95b

This paper is based on jdk1.8 to explore the underlying mechanism of dynamic proxy

Introduction to Java Proxy

There are three kinds of proxy implementations in Java: JDK static proxy, JDK dynamic proxy and CGLIB dynamic proxy. In Spring's AOP implementation, JDK dynamic proxy and CGLIB dynamic proxy are mainly applied. However, this paper focuses on JDK dynamic proxy mechanism, CGLIB dynamic proxy will be explored later.

The general pattern of proxy implementation is JDK static proxy: create an interface, then create the proxy class to implement the interface and implement the abstract method in the interface. Then create a proxy class and make it implement the interface. Hold a reference to a proxy object in the proxy class, and then call the method of that object in the proxy class method.

Actually, the proxy class preprocesses and filters messages for the proxy class, and then forwards the messages to the proxy class, after which it can also post-process the messages. The proxy class and the proxy class usually have an association (that is, the reference to the removed object mentioned above). The proxy class itself does not implement the service, but provides the service by calling the methods in the proxy class.

Static proxy

Interface

Proxy class

proxy class

Test class and output results

As we can see, using JDK static proxy can easily accomplish proxy operations on a class. However, the shortcomings of JDK static proxy are exposed: because the proxy can only serve one class, if there are many kinds of proxy, then a large number of proxy classes need to be written, which is more cumbersome.

Let's use JDK dynamic proxy to do the same thing

JDK Dynamic Agent

Interface

Proxy class

proxy class

Test class and output results

Implementation Principle of JDK Dynamic Agent

JDK dynamic proxy is also implemented by the basic interface. Because the polymorphic way of realizing class instances through interface pointing can effectively decouple the concrete implementation and call, and facilitate later modification and maintenance.

Through the introduction above, we can find that there are some similarities between JDK static proxy and JDK dynamic proxy, such as creating proxy classes and implementing interfaces for proxy classes. But the difference is also obvious - in static proxy, we need to create proxy class for which interface and which proxy class, so we need proxy class to implement the same interface as the proxy class before compiling, and call the corresponding method of the proxy class directly in the method of implementation; but dynamic proxy is different. We don't know which interface to create and which proxy class to create, because it is created at runtime.

Let's sum up the difference between JDK static proxy and JDK dynamic proxy in one sentence, and then start exploring the underlying implementation mechanism of JDK dynamic proxy:
JDK static proxy is created by direct encoding, while JDK dynamic proxy creates proxy classes at runtime using reflection mechanism.
In fact, the core of dynamic proxy is Invocation Handler. Each proxy instance has an associated Invocation Handler. When calling a proxy instance, the method call is coded and assigned to its invoke method of the Invocation Handler. So the invoke method in InvocationHandler is used to invoke the proxy object instance method, and the invoke method determines which method to invoke the proxy according to the incoming proxy object, method name and parameters.

From the test class of JDK dynamic proxy, we can find that the generation of proxy class is accomplished by the new Proxy Instance in the Proxy class. Let's go into this function and see:

New ProxyInstance in the Proxy class

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        //If h is empty, an exception will be thrown
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();//Copy some interfaces implemented by the proxy class for later permission checks
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            //Check some security permissions here to ensure that we have permission to proxy the expected proxy classes
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * The following method generates the proxy class
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Gets the constructor object of the proxy class using the specified call handler
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //If the constructor of the proxy class is private, use reflection to set accessible
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //Generate the object of the proxy class according to the constructor of the proxy class and return it
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

So the proxy class is actually generated by the getProxyClass method:

 /**
     * Generate a proxy class, but you must check permissions before calling this method
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        //If the number of interfaces is greater than 65535, an illegal parameter error is thrown
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If there is a corresponding proxy class in the cache, return it directly
        // Otherwise, the proxy class will have ProxyClassFactory to create
        return proxyClassCache.get(loader, interfaces);
    }

So what is ProxyClassFactory?

   /**
     *  There is a factory function that creates proxy classes based on a given ClassLoader and Interface  
     *
     */
    private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // The prefix of the name of the proxy class is "$Proxy"
        private static final String proxyClassNamePrefix = "$Proxy";

        // Each proxy class prefix is followed by a unique number, such as $Proxy0, $Proxy1, $Proxy2.
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 * Verify that the class loader loads the interface to get whether the object is the same as the object passed in by the application function parameter
                 */
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 * Verify that the Class object is an interface
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that the interface is duplicated
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            String proxyPkg = null;     // Declare the package where the proxy class is located
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             * Record packages with a non-public proxy interface to define proxy classes in the same package. Verify all non-public at the same time
             * The proxy interfaces are all in the same package
             */
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // If all are public proxy interfaces, then the generated proxy class is under com.sun.proxy package.
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * Generate a name package name + prefix + unique number for the proxy class
             * For example, com. sun. proxy. $Proxy 0. class
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate bytecode files for the specified proxy class
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

Byte code generation

From the above code byte [] proxyClassFile = ProxyGenerator. generateProxyClass (proxyName, interfaces, access Flags); you can see that the generation of proxy class bytecode files is actually done through the generateProxyClass method in the ProxyGenerate class.

 public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
       // The real way to generate proxy class bytecode files is here
        final byte[] var4 = var3.generateClassFile();
       // Save the bytecode file of the proxy class
        if(saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction() {
                public Void run() {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if(var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar), 
                                                                                   new String[0]);
                            Files.createDirectories(var3, new FileAttribute[0]);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class", new String[0]);
                        }

                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: " + var4x);
                    }
                }
            });
        }

        return var4;
    }

Let's look at the generateClassFile method that is really used to generate proxy class bytecode files:

private byte[] generateClassFile() {
        //The following series of addProxyMethod methods add methods in interfaces and methods in Object s to proxy methods
        this.addProxyMethod(hashCodeMethod, Object.class);
        this.addProxyMethod(equalsMethod, Object.class);
        this.addProxyMethod(toStringMethod, Object.class);
        Class[] var1 = this.interfaces;
        int var2 = var1.length;

        int var3;
        Class var4;
       //Get all the methods in the interface and add them to the proxy method
        for(var3 = 0; var3 < var2; ++var3) {
            var4 = var1[var3];
            Method[] var5 = var4.getMethods();
            int var6 = var5.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                Method var8 = var5[var7];
                this.addProxyMethod(var8, var4);
            }
        }

        Iterator var11 = this.proxyMethods.values().iterator();
        //Verify that the return types of methods with the same method signature are consistent
        List var12;
        while(var11.hasNext()) {
            var12 = (List)var11.next();
            checkReturnTypes(var12);
        }

        //The next series of steps are used to write the proxy class Class file
        Iterator var15;
        try {
             //Constructors for generating proxy classes
            this.methods.add(this.generateConstructor());
            var11 = this.proxyMethods.values().iterator();

            while(var11.hasNext()) {
                var12 = (List)var11.next();
                var15 = var12.iterator();

                while(var15.hasNext()) {
                    ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
                    //Declare the proxy class field as Method and the field modifier as private static.
                   //Because 10 is ACC_PRIVATE and ACC_STATIC and operation, the fields of the proxy class are private static Method***
                    this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, 
                                   "Ljava/lang/reflect/Method;", 10));
                   //Method of Generating Proxy Classes
                    this.methods.add(var16.generateMethod());
                }
            }
           //Generate static code blocks for proxy classes to initialize certain fields
            this.methods.add(this.generateStaticInitializer());
        } catch (IOException var10) {
            throw new InternalError("unexpected I/O Exception", var10);
        }

        if(this.methods.size() > '\uffff') { //Exceptions are thrown when the number of methods in the proxy class exceeds 65535
            throw new IllegalArgumentException("method limit exceeded");
        } else if(this.fields.size() > '\uffff') {// Exceptions are thrown when the number of fields in the proxy class exceeds 65535
            throw new IllegalArgumentException("field limit exceeded");
        } else {
            // The following is the process of processing files.
            this.cp.getClass(dotToSlash(this.className));
            this.cp.getClass("java/lang/reflect/Proxy");
            var1 = this.interfaces;
            var2 = var1.length;

            for(var3 = 0; var3 < var2; ++var3) {
                var4 = var1[var3];
                this.cp.getClass(dotToSlash(var4.getName()));
            }

            this.cp.setReadOnly();
            ByteArrayOutputStream var13 = new ByteArrayOutputStream();
            DataOutputStream var14 = new DataOutputStream(var13);

            try {
                var14.writeInt(-889275714);
                var14.writeShort(0);
                var14.writeShort(49);
                this.cp.write(var14);
                var14.writeShort(this.accessFlags);
                var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
                var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
                var14.writeShort(this.interfaces.length);
                Class[] var17 = this.interfaces;
                int var18 = var17.length;

                for(int var19 = 0; var19 < var18; ++var19) {
                    Class var22 = var17[var19];
                    var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
                }

                var14.writeShort(this.fields.size());
                var15 = this.fields.iterator();

                while(var15.hasNext()) {
                    ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
                    var20.write(var14);
                }

                var14.writeShort(this.methods.size());
                var15 = this.methods.iterator();

                while(var15.hasNext()) {
                    ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
                    var21.write(var14);
                }

                var14.writeShort(0);
                return var13.toByteArray();
            } catch (IOException var9) {
                throw new InternalError("unexpected I/O Exception", var9);
            }
        }
    }

Method calls of proxy classes

The following is the addProxyMethod method method that adds the interface and some methods in Object to the proxy class:

private void addProxyMethod(Method var1, Class<?> var2) {
        String var3 = var1.getName();//Get the method name
        Class[] var4 = var1.getParameterTypes();//Obtain method parameter type
        Class var5 = var1.getReturnType();//Get the method return type
        Class[] var6 = var1.getExceptionTypes();//Abnormal type
        String var7 = var3 + getParameterDescriptors(var4);//Get method signature
        Object var8 = (List)this.proxyMethods.get(var7);//Obtain the value of proxyMethod before the method
        if(var8 != null) {//Handling method duplication in multiple proxy interfaces
            Iterator var9 = ((List)var8).iterator();

            while(var9.hasNext()) {
                ProxyGenerator.ProxyMethod var10 = (ProxyGenerator.ProxyMethod)var9.next();
                if(var5 == var10.returnType) {
                    ArrayList var11 = new ArrayList();
                    collectCompatibleTypes(var6, var10.exceptionTypes, var11);
                    collectCompatibleTypes(var10.exceptionTypes, var6, var11);
                    var10.exceptionTypes = new Class[var11.size()];
                    var10.exceptionTypes = (Class[])var11.toArray(var10.exceptionTypes);
                    return;
                }
            }
        } else {
            var8 = new ArrayList(3);
            this.proxyMethods.put(var7, var8);
        }

        ((List)var8).add(new ProxyGenerator.ProxyMethod(var3, var4, var5, var6, var2, null));
    }

This is the ultimate real proxy class, which inherits from Proxy and implements the Subject interface we defined. We passed through

HelloInterface helloInterface = (HelloInterface ) Proxy.newProxyInstance(loader, interfaces, handler);
  • 1

The resulting proxy class object is an instance of the above class. Then we execute the following statement:

helloInterface.hello("Tom");
  • 1

In fact, it is the corresponding method to execute the above class, that is:

 public final void hello(String paramString)
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      //Call the invoke method of our custom InvocationHandlerImpl:
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

Notice here the h in this.h.invoke, which is an attribute of the class Proxy

 protected InvocationHandler h;

Because this proxy class inherits Proxy, it inherits this property, and this property value is what we define.

InvocationHandler handler = new InvocationHandlerImpl(hello);
  • 1

At the same time, we also find that the first parameter of invoke method passes in this when invoked at the bottom, that is, ProxySubject, which is generated dynamically by JVM itself, rather than by our own defined proxy object.

Deep Understanding of CGLIB Dynamic Agent Mechanism

What is Cglib

Cglib is a powerful, high-performance code generation package, which is widely used by many AOP frameworks to provide them with methods of interception. The following picture is a Cglib that I found on the Internet and its relationship with some frameworks and languages:

Summarize this picture:

  • The bottom layer is Bytecode, which is a virtual instruction format produced by Java to ensure "compile once and run everywhere", such as iload_0, iconst_1, if_icmpne, dup, etc.
  • On top of bytecode is ASM, which is a framework for directly manipulating bytecode. Applying ASM requires familiarity with Java bytecode and Class structure.
  • Above ASM are CGLIB, Groovy and Bean Shell. The latter two are not the content of Java system, but scripting language. They generate bytecode to execute Java code in disguised form through ASM framework. This shows that it is not necessary to write Java code to execute program in JVM. As long as you can generate Java bytecode, JVM does not care about bytecode. Source, of course, the JVM bytecode generated by Java code is generated directly by the compiler, which is the most "orthodox" JVM bytecode.
  • On top of CGLIB, Groovy and Bean Shell are the frameworks of Hibernate and Spring AOP, which are familiar to everyone.
  • The top level is Applications, which is a specific application, usually a Web project or running a program locally.

This paper is based on CGLIB 3.1 to explore.

cglib is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime.

In Spring AOP, it is usually used to generate AopProxy objects. Not only that, but also the generation of PO(Persistant Object Persistent Object) bytecode in Hibernate depends on it.

This article will deeply explore the implementation mechanism of CGLIB dynamic agent, with the following article together eating better taste:
Deep understanding of JDK dynamic proxy mechanism

CGLIB dynamic proxy example

Let's start with a simple example of CGLIB dynamic proxy:

In order for subsequent coding to work smoothly, we need to use Maven to introduce CGLIB packages.

Figure 1.1 Proxy class

Figure 1.2 Implementation of Method Interceptor Interface Generation Method Interceptor

Figure 1.3 Generates the proxy class object and prints the execution results after the proxy class object invokes the method

JDK proxy requires that the class to be proxied must implement interfaces, which has strong limitations. CGLIB dynamic proxy does not have such mandatory requirements. Simply put, CGLIB will make the generated proxy class inherit the proxy class, and strengthen the proxy method in the proxy class (pre-processing, post-processing, etc.). At the bottom of CGLIB, it actually relies on ASM, a very powerful Java bytecode generation framework.

Generating proxy class objects

As we can see from Figure 1.3, the proxy class object is created by the Enhancer class. Enhancer is a bytecode enhancer of CGLIB, which can easily expand classes. In Figure 1.3, Superclass is set for classes.

Several steps to create a proxy object:

  • Generate the binary bytecode file of the proxy class;
  • Load the binary bytecode to generate the Class object (for example, using the Class.forName() method);
  • Obtain instance construction through reflection mechanism and create proxy class objects

Let's look at the Java code after decompiling the proxy class Class file

package proxy;

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 HelloServiceImpl$EnhancerByCGLIB$82ef2d06
  extends HelloServiceImpl
  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$sayHello$0$Method;
  private static final MethodProxy CGLIB$sayHello$0$Proxy;
  private static final Object[] CGLIB$emptyArgs;
  private static final Method CGLIB$finalize$1$Method;
  private static final MethodProxy CGLIB$finalize$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;

  static void CGLIB$STATICHOOK1()
  {
    CGLIB$THREAD_CALLBACKS = new ThreadLocal();
    CGLIB$emptyArgs = new Object[0];
    Class localClass1 = Class.forName("proxy.HelloServiceImpl$EnhancerByCGLIB$82ef2d06");
    Class localClass2;
    Method[] tmp95_92 = ReflectUtils.findMethods(new String[] { "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (localClass2 = Class.forName("java.lang.Object")).getDeclaredMethods());
    CGLIB$finalize$1$Method = tmp95_92[0];
    CGLIB$finalize$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "finalize", "CGLIB$finalize$1");
    Method[] tmp115_95 = tmp95_92;
    CGLIB$equals$2$Method = tmp115_95[1];
    CGLIB$equals$2$Proxy = MethodProxy.create(localClass2, localClass1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
    Method[] tmp135_115 = tmp115_95;
    CGLIB$toString$3$Method = tmp135_115[2];
    CGLIB$toString$3$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
    Method[] tmp155_135 = tmp135_115;
    CGLIB$hashCode$4$Method = tmp155_135[3];
    CGLIB$hashCode$4$Proxy = MethodProxy.create(localClass2, localClass1, "()I", "hashCode", "CGLIB$hashCode$4");
    Method[] tmp175_155 = tmp155_135;
    CGLIB$clone$5$Method = tmp175_155[4];
    CGLIB$clone$5$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
    tmp175_155;
    Method[] tmp223_220 = ReflectUtils.findMethods(new String[] { "sayHello", "()V" }, (localClass2 = Class.forName("proxy.HelloServiceImpl")).getDeclaredMethods());
    CGLIB$sayHello$0$Method = tmp223_220[0];
    CGLIB$sayHello$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "sayHello", "CGLIB$sayHello$0");
    tmp223_220;
    return;
  }

  final void CGLIB$sayHello$0()
  {
    super.sayHello();
  }

  public final void sayHello()
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    if (this.CGLIB$CALLBACK_0 != null) {
      return;
    }
    super.sayHello();
  }

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

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

  final boolean CGLIB$equals$2(Object paramObject)
  {
    return super.equals(paramObject);
  }

  public final boolean equals(Object paramObject)
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null)
    {
      Object tmp41_36 = tmp17_14.intercept(this, CGLIB$equals$2$Method, new Object[] { paramObject }, CGLIB$equals$2$Proxy);
      tmp41_36;
      return tmp41_36 == null ? false : ((Boolean)tmp41_36).booleanValue();
    }
    return super.equals(paramObject);
  }

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

  public final String toString()
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null) {
      return (String)tmp17_14.intercept(this, CGLIB$toString$3$Method, CGLIB$emptyArgs, CGLIB$toString$3$Proxy);
    }
    return super.toString();
  }

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

  public final int hashCode()
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null)
    {
      Object tmp36_31 = tmp17_14.intercept(this, CGLIB$hashCode$4$Method, CGLIB$emptyArgs, CGLIB$hashCode$4$Proxy);
      tmp36_31;
      return tmp36_31 == null ? 0 : ((Number)tmp36_31).intValue();
    }
    return super.hashCode();
  }

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

  protected final Object clone()
    throws CloneNotSupportedException
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null) {
      return tmp17_14.intercept(this, CGLIB$clone$5$Method, CGLIB$emptyArgs, CGLIB$clone$5$Proxy);
    }
    return super.clone();
  }

  public static MethodProxy CGLIB$findMethodProxy(Signature paramSignature)
  {
    String tmp4_1 = paramSignature.toString();
    switch (tmp4_1.hashCode())
    {
    case -1574182249: 
      if (tmp4_1.equals("finalize()V")) {
        return CGLIB$finalize$1$Proxy;
      }
      break;
    }
  }

  public HelloServiceImpl$EnhancerByCGLIB$82ef2d06()
  {
    CGLIB$BIND_CALLBACKS(this);
  }

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

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

  private static final void CGLIB$BIND_CALLBACKS(Object paramObject)
  {
    82ef2d06 local82ef2d06 = (82ef2d06)paramObject;
    if (!local82ef2d06.CGLIB$BOUND)
    {
      local82ef2d06.CGLIB$BOUND = true;
      Object tmp23_20 = CGLIB$THREAD_CALLBACKS.get();
      if (tmp23_20 == null)
      {
        tmp23_20;
        CGLIB$STATIC_CALLBACKS;
      }
      local82ef2d06.CGLIB$CALLBACK_0 = (// INTERNAL ERROR //

Acting on the Principal Class

We posted the generated proxy class source code above. With our example above as a reference, let's summarize what CGLIB has done when acting as an agent.

  • The generated proxy class HelloServiceImpl$EnhancerByCGLIBef2d06 inherits the proxy class HelloServiceImpl. Here we need to pay attention to one point: if the delegate class is modified by final, then it can not be inherited, that is, it can not be proxyed; similarly, if there is a final modification method in the delegate class, then the method can not be proxyed;
  • The proxy class generates two methods for the delegate method, one is the overridden sayHello method, and the other is the CGLIB$sayHello CGLIB $sayHello $0 method. We can see that it calls the sayHello method of the parent class directly.
  • When executing the sayHello method of the proxy object, it first determines whether there is CGLIB$CALLBACK_0 that implements the Method Interceptor interface; if there is, it calls the intercept method in the Method Interceptor, as shown in Figure 2.1.

Figure 2.1 Interept method

Figure 2.2 The proxy class generates two methods for each delegation method

In the intercept method, in addition to calling the delegate method, we also do some enhancements. In Spring AOP, a typical application scenario is to log operations before and after the execution of some sensitive methods.

As we can see from Figure 2.1, the invoke delegation method is executed by invokeSuper method invoked by the MethodProxy object of the proxy method. Now let's look at the mystery of invokeSuper method:

Figure 2.3 invokeSuper method

It seems that the proxy method invocation cannot be seen directly here. That's all right. I'll introduce it slowly.
We know that method invocation in JDK dynamic proxy is done by reflection. If you don't know much about it, you can check out my previous blog. Deep understanding of JDK dynamic proxy mechanism . But in CGLIB, method invocation is not done by reflection, but by direct method invocation: FastClass handles lass objects in a special way, such as storing method references with arrays, and maintains method references by an index subscript every time a method is invoked. For example, the following getIndex method uses method signatures to obtain the subscripts of the method in an array that stores Class information.

Figure 2.4 getIndex method

Figure 2.5 A reference to two FastClass objects in the FastClassInfo class. png

Take our sayHello method as an example, f1 points to the delegate class object, f2 points to the proxy class object, i1 and i2 represent the subscripts of the sayHello method and CGLIB$sayHello CGLIB $sayHello $0 method in the object information array, respectively.

So far, the CGLIB dynamic proxy mechanism has been introduced, and the comparison among the three proxy modes is given below.

Agency mode Realization Advantage shortcoming Characteristic
JDK static proxy The proxy class and the delegate class implement the same interface, and the hard-coded interface is needed in the proxy class. Simple to implement and easy to understand Proxy classes require hard-coded interfaces, which may lead to duplicate coding, waste of storage space and low efficiency in practical applications. It doesn't seem to have any characteristics.
JDK Dynamic Agent The proxy class and the delegate class realize the same interface, mainly through the proxy class to realize InvocationHandler and rewrite invoke method for dynamic proxy. In the invoke method, the method will be enhanced. No hard-coded interface, high code reuse rate Delegate classes that can only proxy interfaces The underlying use of reflection mechanism for method invocation
CGLIB Dynamic Agent The proxy class takes the delegate class as its parent class and creates two methods for the non-final delegate method. One is the same method as the signature of the delegate method, which calls the delegate method through super in the method, and the other is the unique method of the proxy class. In the proxy method, it determines whether there is an object that implements the Method Interceptor interface, and if so, it calls the intercept method to proxy the delegate method. Classes or interfaces can be enhanced at runtime, and delegated classes do not need to implement interfaces You cannot proxy final classes and final methods The bottom layer stores all methods in an array and makes method calls directly through the array index.

The author of Wechat Public No. Huang Xiaoxiao is an engineer of Ant Golden Wear JAVA, focusing on JAVA.
Back-end technology stack: SpringBoot, SSM family bucket, MySQL, distributed, middleware, micro services, but also know how to invest in financial management, adhere to learning and writing, believe in the power of lifelong learning! Respond to "Architect" after paying attention to the public number.
Free learning materials such as Java Foundation, Advancement, Project and Architect, as well as popular technology learning videos such as database, distributed, micro-service, etc., are rich in content, taking into account the principles and practices. In addition, the author's original Java learning guide, Java programmer interview guide and other dry goods resources will also be presented.

Tags: Java JDK jvm Spring

Posted on Sun, 25 Aug 2019 02:46:22 -0700 by doodmon