Spring AOP is actually implemented based on dynamic proxies, but Spring supports both JDK Proxy and Cglib. Let’s take a look at the two ways to implement dynamic proxies

Note: JDK1.8 is used in this example

Dynamic proxy code examples

The JDK Proxy way

/** * The handler class when the agent's interface is called */
class MyHandler implements InvocationHandler {
    private final Object target;
    public MyHandler(final Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("-----before----");
        final Object o = method.invoke(this.target, args);
        System.out.println("-----after----");
        returno; }}public static void main(final String[] args) {
        final BizAImpl bizAImpl = new BizAImpl();
        final IBizA newBizA = (IBizA) 		Proxy.newProxyInstance(MyHandler.class.getClassLoader(),
                bizAImpl.getClass().getInterfaces(),
                new MyHandler(bizAImpl));
        newBizA.doSomething();
}
Copy the code

Additional way

class MyHandler implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("----before----");
        proxy.invokeSuper(obj, args);
        System.out.println("----after----");
        returnobj; }}public static void main(String[] args) {
    MyHandler myHandler = new MyHandler();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(BizA.class);
    enhancer.setCallback(myHandler);

    BizA bizA = (BizA) enhancer.create();
    bizA.doSomething();
}
Copy the code

Compare the use of JDK Proxy and Cglib

From the sample code, we can draw the following conclusions

JDK Proxy Cglib
Proxy classes are required to implement interfaces, see IBizA and BizImpl A proxy that supports classes with no interface implementation requirements
The need to pass the proxy target object (bizImpl) to the InvocationHandler is not flexible enough to write Target proxy objects can be generated directly from the class (biza.class)

Implementation principle of dynamic proxy

In fact, the two proxies are implemented quite differently

Implementation principle of JDK Proxy

JDK Proxy copies the original Proxy class and generates a new class. When the new class is generated, the invocation of the method is transferred to The InvocationHandler. When the Proxy class executes the method, it actually calls the Invoke method of InvocationHandler.

Let’s take a look at the JDK source, starting with the newProxyInstance method

public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h) {
    	/* * Look up or generate the designated proxy class. */Class<? > cl = getProxyClass0(loader, intfs);/* * Invoke its constructor with the designated invocation handler. */
    	finalConstructor<? > cons = cl.getConstructor(constructorParams);return cons.newInstance(new Object[]{h});
    
}
Copy the code

The newProxyInstance method does only two things

  • Getting proxy classes
  • Call the constructor of the proxy class and pass in handler as an argument (you can see this by decompiling the class file)

So the point here is how to get the proxy class, so let’s move on

private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    // If the proxy class defined by the given loader implementing
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory
    return proxyClassCache.get(loader, interfaces);
}
Copy the code

It is preferentially obtained from the cache. If it is not in the cache, it will be generated through the ProxyClassFactory. The cached part of the code will be skipped and the process of generating proxy classes will be directly watched

/** * A factory function that generates, defines and returns the proxy class given * the ClassLoader and array of interfaces. */ private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<? >[], Class<? >> { @Override public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { / * * Choose a name for the proxy class to generate the. * / / / generated proxy class name long num = nextUniqueNumber. GetAndIncrement (); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate 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()); }}}Copy the code

Extract the core method

// Generate bytecode data
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
// This is a native method
return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
Copy the code

Enter the method ProxyGenerator. GenerateProxyClass ()

	public static byte[] generateProxyClass(finalString name, Class<? >[] interfaces,int accessFlags)
    {
        ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
        final byte[] classFile = gen.generateClassFile();
		
        // This flag is a configuration of the system, whether to save the generated file, can be through the configuration item
        . / / sun. Misc. ProxyGenerator saveGeneratedFiles get
        if (saveGeneratedFiles) {
            / /... Save the file. It's not the point
        }

        return classFile;
    }
Copy the code

The core method for generating class files

	private byte[] generateClassFile() {

        /* ============================================================ * Step 1: Assemble ProxyMethod objects for all methods to * generate proxy dispatching code for
        //object public method
        addProxyMethod(hashCodeMethod, Object.class);
        addProxyMethod(equalsMethod, Object.class);
        addProxyMethod(toStringMethod, Object.class);

		// The interface method of the proxy implementation
        for(Class<? > intf : interfaces) {for(Method m : intf.getMethods()) { addProxyMethod(m, intf); }}/* ============================================================ * Step 2: Assemble FieldInfo and MethodInfo structs for all of * fields and methods in the class we are generating. Converts a proxy method (MethodProxy) to a method (MethodInfo), which implements writing bytecode */
        try {
            methods.add(generateConstructor());
            for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
                for (ProxyMethod pm : sigmethods) {

                    // add static field for method's Method object
                    fields.add(new FieldInfo(pm.methodFieldName,
                        "Ljava/lang/reflect/Method;",
                         ACC_PRIVATE | ACC_STATIC));
					
                    // MethodProxy converts to MethodInfo
                    // generate code for proxy method and add it
                    methods.add(pm.generateMethod());
                }
            }
            methods.add(generateStaticInitializer());
        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception", e);
        }

        / * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = * Step 3: Write the final class file. * Step 3: Write the class file * /

        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DataOutputStream dout = new DataOutputStream(bout);

        try {
            /* * Write all the items of the "ClassFile" structure. * See JVMS section 4.1. */
            / /... Bytecode generation according to the JVM specification, omitted

        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception", e);
        }

        return bout.toByteArray();
    }
Copy the code

GenerateMethod () converts MethodInfo from ProxyMethod to MethodInfo

	out.writeByte(opc_invokeinterface);
    out.writeShort(cp.getInterfaceMethodRef(
    "java/lang/reflect/InvocationHandler"."invoke"."(Ljava/lang/Object; Ljava/lang/reflect/Method;" +
    "[Ljava/lang/Object;)Ljava/lang/Object;"));
    out.writeByte(4);
    out.writeByte(0);
Copy the code

$proxy0.class = $proxy0.class = $proxy0.class

package com.sun.proxy;

import com.proxy.IBizA;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class Proxy0 extends Proxy
  implements IBizA
{
  public Proxy0(a)
    throws 
  {
    super(paramInvocationHandler);
  }

  public final void doSomething(a)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return; }}public final String toString(a)
    throws 
  {
    try
    {
      return ((String)this.h.invoke(this, m2, null)); }}}Copy the code

As we can see from the decompilated file, the proxy class actually calls the Invoke method of InvocationHandler

Note: The system does not output the agent class file by default. If you want to implement the output of the new agent class file, you need to add it to the main method

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles"."true");
Copy the code

Note: Proxy code can be seen directly from JDK source code, ProxyGenerator code please refer to:

Github.com/JetBrains/j…

The realization principle of CGLIB

Cglib uses the ASM library to generate proxy classes. Cglib generates two classes, one for proxy classes and one for method calls. When calling a method, cglib optimizes it by adding an index to each proxy method instead of using reflection to call the method, and then finding the method based on the index and calling it directly.

Note: the asm is a Java bytecode library operation Reference: https://www.ibm.com/developerworks/cn/java/j-lo-asm30/index.html https://asm.ow2.io/Copy the code

Generation of proxy classes

Let’s first take a look at how cglib generates the proxy class. Since cglib’s code is more complex, we’ll only post the key parts.

Cglib is similar to JDK proxy in that both cglib and JDK proxy are first fetched from the cache. If not, the method that generates the class is called. Let’s look at the Generate method of AbstractClassGenerator

protected Class generate(ClassLoaderData data) {
        Class gen;
        Object save = CURRENT.get();
        CURRENT.set(this);
        try {
            ClassLoader classLoader = data.getClassLoader();
            synchronized (classLoader) {
              String name = generateClassName(data.getUniqueNamePredicate());              
              data.reserveName(name);
              this.setClassName(name);
            }
            if (attemptLoad) {
                try {
                    gen = classLoader.loadClass(getClassName());
                    return gen;
                } catch (ClassNotFoundException e) {
                    // ignore}}// Call strategy's generate method
            byte[] b = strategy.generate(this);
            String className = ClassNameReader.getClassName(new ClassReader(b));
            ProtectionDomain protectionDomain = getProtectionDomain();
            synchronized (classLoader) { // just in case
                if (protectionDomain == null) {
                    gen = ReflectUtils.defineClass(className, b, classLoader);
                } else{ gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain); }}return gen;
        } catch (RuntimeException e) {
            throw e;
        } catch (Error e) {
            throw e;
        } catch (Exception e) {
            throw new CodeGenerationException(e);
        } finally{ CURRENT.set(save); }}Copy the code

GeneratorStrategy’s generate method is called, and the default policy is DefaultGeneratorStrategy, which we continue to trace

public byte[] generate(ClassGenerator cg) throws Exception {
    DebuggingClassWriter cw = getClassVisitor();
    transform(cg).generateClass(cw);
    return transform(cw.toByteArray());
}
protected ClassGenerator transform(ClassGenerator cg) throws Exception {
    return cg;
}
Copy the code

The default policy does no real work and calls the generateClass method of the ClassGenerator interface directly.

Going back to AbstractClassGenerator class, this class does not define the generateClass method, but does define the generateClass method in its implementation class Enhancer, which actually calls the Enhancer method as well.

public void generateClass(ClassVisitor v) throws Exception {
        Class sc = (superclass == null)? Object.class : superclass;// Order is very important: must add superclass, then
        // its superclass chain, then each interface and
        // its superinterfaces.
        List actualMethods = new ArrayList();
        List interfaceMethods = new ArrayList();
        final Set forcePublic = new HashSet();
        getMethods(sc, interfaces, actualMethods, interfaceMethods, forcePublic);

        List methods = CollectionUtils.transform(actualMethods, new Transformer() {
            public Object transform(Object value) {
                Method method = (Method)value;
				/ /...
                returnReflectUtils.getMethodInfo(method, modifiers); }});// Start of class
        ClassEmitter e = new ClassEmitter(v);
        e.begin_class(Constants.V1_8,
                      Constants.ACC_PUBLIC,
                      getClassName(),
                      Type.getType(sc),
                      (useFactory ?
                       TypeUtils.add(TypeUtils.getTypes(interfaces), FACTORY) :
                       TypeUtils.getTypes(interfaces)),
                      Constants.SOURCE_FILE);
		
    	// Declare attributes
        e.declare_field(Constants.ACC_PRIVATE, BOUND_FIELD, Type.BOOLEAN_TYPE, null);
        e.declare_field(Constants.ACC_PUBLIC | Constants.ACC_STATIC, FACTORY_DATA_FIELD, OBJECT_TYPE, null);
        e.declare_field(Constants.PRIVATE_FINAL_STATIC, THREAD_CALLBACKS_FIELD, THREAD_LOCAL, null);
        e.declare_field(Constants.PRIVATE_FINAL_STATIC, STATIC_CALLBACKS_FIELD, CALLBACK_ARRAY, null);

        // This is declared private to avoid "public field" pollution
        e.declare_field(Constants.ACC_PRIVATE | Constants.ACC_STATIC, CALLBACK_FILTER_FIELD, OBJECT_TYPE, null);
		// constructor
        emitDefaultConstructor(e);
    	
    	// Implement the callback
        emitSetThreadCallbacks(e);
        emitSetStaticCallbacks(e);
        emitBindCallbacks(e);

        / /...
		
    	// End of class
        e.end_class();
    }
Copy the code

The core of this code is to use ClassEmitter (an ENCAPSULATION of asm ClassVisitor) to construct a class that implements the methods of the proxy target class (BizA) and then adds calls to the method interceptor, as we can see from the decomcompiled code

public final String doSomething(a)
  {
    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$doSomething$0$Method, CGLIB$emptyArgs, CGLIB$doSomething$0$Proxy));
    return super.doSomething();
  }
Copy the code

Notice this code

tmp17_14.intercept(this, CGLIB$doSomething$0$Method, CGLIB$emptyArgs, CGLIB$doSomething$0$Proxy));
Copy the code

Implementation of a MethodInterceptor call.

Along with generating the proxy class, Cglib also generates another class, FastClass, which is described in more detail below

Invocation of the proxy class

From the cglib code example, we see that we actually call this in the Intercept method

proxy.invokeSuper(obj, args);
Copy the code

Let’s take a closer look at what does this method do

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) {
        throwe.getTargetException(); }}Copy the code

Ignoring init, you are actually calling the FastClass invoke method

/**
     * Invoke the method with the specified index.
     * @see getIndex(name, Class[])
     * @param index the method index
     * @param obj the object the underlying method is invoked from
     * @param args the arguments used for the method call
     * @throws java.lang.reflect.InvocationTargetException if the underlying method throws an exception
     */
    abstract public Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException;
Copy the code

This is an abstract method implemented in a dynamically generated FastClass subclass (decompiled bytecode will be posted later). From the method description, we can see that FastClass calls different methods based on different indexes (note that this is a direct call rather than reflection). Let’s look at the decompiled code

public Object invoke(, Object paramObject, Object[] paramArrayOfObject)
    throws InvocationTargetException
  {
    // Byte code:
    // 0: aload_2
    //   1: checkcast 133	com/cglib/BizA?EnhancerByCGLIB?eca2fdc
    // 4: iload_1
    // 5: tableswitch default:+304 -> 309, 0:+99->104, 1:+114->119....
    
    //....
    
    //   198: invokevirtual 177	com/cglib/BizA?EnhancerByCGLIB?eca2fdc:doSomething	()Ljava/lang/String;
	
    
   	/ /...
    
    // 272: aconst_null
    // 273: areturn
    //   274: invokevirtual 199	com/cglib/BizA?EnhancerByCGLIB?eca2fdc:CGLIB$toString$2	()Ljava/lang/String;
    // 277: areturn
    //   278: invokevirtual 201	com/cglib/BizA?EnhancerByCGLIB?eca2fdc:CGLIB$clone$4	()Ljava/lang/Object;
    // 281: areturn
    // 330: athrow
    //
    // Exception table:
    // from to target type
    // 5 312 312 java/lang/Throwable
  }
Copy the code

We see that tableswitch is used, which you can think of as a switch statement. Call different methods based on different values. From line 198 we can see that the doSomething method is called.

This is a major advantage of CGLIb, which reduces reflection by creating indexes and improves the performance of dynamic proxies. Cglib code also makes extensive use of caching, lazy loading, and other mechanisms to improve performance.

The code for Cglib is complex, and for reasons of length, we will only briefly explain it from the perspective of dynamic proxy implementation. Cglib code quality is very high, which uses a lot of design patterns, for thread safety, caching, lazy loading, etc., are involved, interested students can directly read the source code, I posted a link to the source code in the appendix.

Note: by setting the output directory, additional can be generated class output System. SetProperty (DebuggingClassWriter DEBUG_LOCATION_PROPERTY,"C:\\classes");
Copy the code

Compare the JDK Proxy and cglib implementations

After studying the respective implementation principles, let’s make a comparison again

JDK Proxy cglib
By manipulating bytecode, a call to InvocationHandler is inserted into the generated class On the basis of ASM library, further abstract encapsulation, then generate proxy class (Enhancer proxy) and FastClass. Of course, Cglib is also essentially manipulating bytecode.
The proxy interface method is called by reflection, which has low reflection performance Call a method directly based on a self-built index for high performance

Note: Think about it, why does reflection affect performance?

Reference data

cglib:github.com/cglib/cglib

Spring AOP: docs. Spring. IO/Spring/docs…

Spring Source Code: github.com/spring-proj…