preface

This article will analyze the principle and difference between JDK agent and CGLIB agent from the source point of view.

Dynamic proxy implementation

There are two common dynamic proxy implementation methods, one is JDK dynamic proxy, the other is CGLIB dynamic proxy.

JDK dynamic proxies are Java’s built-in methods that use reflection to generate an anonymous class that implements the proxy interface and InvokeHandler before executing the specific method. A JDK proxy can only proxy the target class that implements the interface, and you cannot use a JDK proxy if the target class does not implement the interface. Because the generated Proxy class inherits the Proxy parent class, and because Java does not support multiple inheritance, the Proxy class can only be generated by implementing the interface implemented by the target class.

The CGLIB dynamic proxy uses the ASM open source package to load the class file of the proxy object class, and then use bytecode technology to modify the bytecode generation subclass of the class file to implement the proxy class. Cglib proxies are not limited to interfaces. Proxy classes are subclassed as long as the proxied class is not final.

JDK

demo

IUserService & UserService

public interface IUserService {
    void login(a);
}

public class UserService implements IUserService {
    @Override
    public void login(a) {
        System.out.println(UserSerivce Implementation class); }}Copy the code

ProxyUserService

public class ProxyUserService {

    private IUserService userService;

    public ProxyUserService(IUserService userService) {
        this.userService = userService;
    }

    public Object proxyInstance(a) {
        return Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                System.out.println("jdk proxy before");
                Object invoke = method.invoke(userService, objects);
                System.out.println("jdk proxy after");
                returninvoke; }}); }}Copy the code

Client

public class Client {
    public static void main(String[] args) {
        // Save the bytecode file of the generated proxy class
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles"."true");

        IUserService userService = new UserService();
        IUserService proxyUserService = (IUserService) newProxyUserService(userService).proxyInstance(); proxyUserService.login(); }}Copy the code

The source code to achieve

newProxyInstance()
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
     throws IllegalArgumentException
 {
     Objects.requireNonNull(h);
 
     finalClass<? >[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();
     if(sm ! =null) {
         checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
     }

     /* * Look up or generate the designated proxy class. */
     // Generate a bytecode file for the interface proxy classClass<? > cl = getProxyClass0(loader, intfs);/* * Invoke its constructor with the designated invocation handler. */
     // Call the constructor to get an instance of the proxy class object, using a custom InvocationHandler as an argument
     try {
         if(sm ! =null) {
             checkNewProxyPermission(Reflection.getCallerClass(), cl);
         }

         finalConstructor<? > cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;
         if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {
                 public Void run(a) {
                     cons.setAccessible(true);
                     return null; }}); }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 newInternalError(t.toString(), t); }}catch (NoSuchMethodException e) {
         throw newInternalError(e.toString(), e); }}Copy the code

GetProxyClass0 (loader, intfs);

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
    // If the proxy class of the corresponding interface exists in the cache, it is returned directly. Otherwise, use ProxyClassFactory to create the proxy class
    return proxyClassCache.get(loader, interfaces);
}
Copy the code

Go to proxyClassCache. Get (loader, interfaces);

public V get(K key, P parameter) {...// If there is no proxy class in the cache, create a proxy class by calling ProxyClassFactoryObject subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); . }Copy the code

The ProxyClassFactory class is a static inner class of Proxy. Go to the Apply method at ProxyClassFactory

private static final class ProxyClassFactory
    implements BiFunction<ClassLoader.Class<? > [].Class<? >>{
    // The proxy class name prefix
    private static final String proxyClassNamePrefix = "$Proxy";

    // Generate a counter for the proxy class name
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

	@Override
	publicClass<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet =new IdentityHashMap<>(interfaces.length);
	    for(Class<? > intf : interfaces) {/* * Verify that the class loader resolves the name of this * interface to the same Class object. */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");
	        }
	        
	        // Check whether the class is an interface type
	        if(! interfaceClass.isInterface()) {throw new IllegalArgumentException(
	                interfaceClass.getName() + " is not an interface");
	        }
	        // Verify that this interface is duplicated
	        if(interfaceSet.put(interfaceClass, Boolean.TRUE) ! =null) {
	            throw new IllegalArgumentException(
	                "repeated interface: " + interfaceClass.getName());
	        }
	    }
	
	    String proxyPkg = null;     // package to define proxy class in
	    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
	
		// The package name of the proxy class is the same as the package name of the interface
	    for(Class<? > intf : interfaces) {// Get the interface access modifier
	        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"); }}}// public interface uses the specified package name
	    if (proxyPkg == null) {
	        // if no non-public proxy interfaces, use com.sun.proxy package
	        proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
	    }
	    // Generate the class name for the proxy class
	    long num = nextUniqueNumber.getAndIncrement();
	    String proxyName = proxyPkg + proxyClassNamePrefix + num;
	
	    // Generate proxy class bytecode files
	    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
	        proxyName, interfaces, accessFlags);
	    try {
	    	// Load the bytecode file of the proxy class into the JVM
	        return defineClass0(loader, proxyName,
	                            proxyClassFile, 0, proxyClassFile.length);
	    } catch (ClassFormatError e) {
	        throw newIllegalArgumentException(e.toString()); }}}Copy the code

Generate bytecode file into the ProxyGenerator. GenerateProxyClass (proxyName, interfaces, accessFlags); This class is not open source and can be decompiled and viewed using an IDE.

public static byte[] generateProxyClass(finalString var0, Class<? >[] var1,int var2) {
  ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
     final byte[] var4 = var3.generateClassFile();
     // Whether to save the bytecode file that generated the proxy class to disk
     if (saveGeneratedFiles) {
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
             public Void run(a) {
                 try {
                     int var1 = var0.lastIndexOf(46);
                     Path var2;
                     if (var1 > 0) {
                         Path var3 = Paths.get(var0.substring(0, var1).replace('. ', File.separatorChar));
                         Files.createDirectories(var3);
                         var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                     } else {
                         var2 = Paths.get(var0 + ".class");
                     }
					 // Write the bytecode to the file
                     Files.write(var2, var4, new OpenOption[0]);
                     return null;
                 } catch (IOException var4x) {
                     throw new InternalError("I/O exception saving generated file: "+ var4x); }}}); }return var4;
 }
Copy the code
Proxy object bytecode file

What is in the decompiled bytecode file of the generated proxy class

public final class $Proxy0 extends Proxy implements IUserService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}public final String toString(a) throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final void login(a) throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final int hashCode(a) throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.dmsd.proxy.jdk.IUserService").getMethod("login");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

You can see that the bytecode file for the generated proxy class contains the following:

  • The Proxy class inherits the Proxy and implements the interface implemented by the target class
  • Rewrite equals(), hashCode (), toString()
  • In a static code block, the methods that need to be overridden and called are retrieved through reflection
  • Execute the target method login() in the target class using the invoke of the parent class’s InvocationHandler object

CGLIB

ASM

ASM is a general-purpose Java bytecode manipulation and analysis framework that can be used to modify existing class files or dynamically generate class files. CGLIB uses the ASM framework to enhance the target class’s bytecode generation proxy class.

The ASM-related API is in the org.spring Framework. ASM package in spring-core.jar.

demo

UserService

public class UserService {
    public void login(a) {
        System.out.println(UserSerivce Implementation class); }}Copy the code

MyIntercepter

Client

public class Client {
    public static void main(String[] args) {
        // Save the bytecode file of the generated proxy class
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./proxyByCglib");
        // Use cglib dynamic proxy to get proxy objects
        Enhancer enhancer = new Enhancer();
        // Set the enhancer parent class
        enhancer.setSuperclass(UserService.class);
        // Set the enhancer callback object
        enhancer.setCallback(new MyIntercepter());
        // Create a proxy object
        UserService proxyUserService = (UserService) enhancer.create();
        // Call the target method through the proxy objectproxyUserService.login(); }}Copy the code

Source code analysis

Using the cglib proxy, you must customize the Interceptor interface to implement the MethodInterceptor interface.

public interface MethodInterceptor extends Callback
{
    /** * All generated proxied methods call this method instead of the original method. * The original method may either be  invoked by normal reflection using the Method object, * or by using the MethodProxy (faster). *@param obj "this", the enhanced object
     * @param method intercepted Method
     * @param args argument array; primitive types are wrapped
     * @param proxy used to invoke super (non-intercepted method); may be called
     * as many times as needed
     * @throws Throwable any exception may be thrown; if so, super method will not be invoked
     * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
     * @see MethodProxy
     */    
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable;
 
}
Copy the code

The MethodInterceptor interface has only one Intercept (…) Method, where the parameters are:

  • Obj: proxy object
  • Method: Intercepted method, target method
  • Args: Parameters that represent methods to be intercepted
  • Proxy: Method object that triggers the parent class

First create the Enhancer object, set the Superclass Superclass and the custom Callback object, and then call the create() method of the parent class AbstractClassGenerator to generate subclasses of the Superclass.

public class Enhancer extends AbstractClassGenerator {
	public Object create(a) {
		classOnly = false;
		argumentTypes = null;
		returncreateHelper(); }}Copy the code

Into the createHelper ();

private Object createHelper(a) {
	// Check callbackTypes and filters
	preValidate();
	// Create the EnhancerKey objectObject key = KEY_FACTORY.newInstance((superclass ! =null)? superclass.getName() :null,
			ReflectUtils.getNames(interfaces),
			filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
			callbackTypes,
			useFactory,
			interceptDuringConstruction,
			serialVersionUID);
	this.currentKey = key;
	// Call the parent method to create
	Object result = super.create(key);
	return result;
}
Copy the code

Enhancer AbstractClassGenerator create()

protected Object create(Object key) {
	try {
		// Get the current class loader, apply the class loader
		ClassLoader loader = getClassLoader();
		Map<ClassLoader, ClassLoaderData> cache = CACHE;
		// Get classloader data from the cache
		ClassLoaderData data = cache.get(loader);
		if (data == null) {
			// Synchronization locks classes
			synchronized (AbstractClassGenerator.class) {
				cache = CACHE;
				data = cache.get(loader);
				if (data == null) {
					Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
					data = new ClassLoaderData(loader);
					// Put the classloader in the new cachenewCache.put(loader, data); CACHE = newCache; }}}this.key = key;
		// Call the get method to get the bytecode, or create it if none exists
		Object obj = data.get(this, getUseCache());
		if (obj instanceof Class) {
			// Use reflection to create proxy objects
			return firstInstance((Class) obj);
		}
		return nextInstance(obj);
	}
	catch (RuntimeException | Error ex) {
		throw ex;
	}
	catch (Exception ex) {
		throw newCodeGenerationException(ex); }}Copy the code

AbstractClassGenerator get(this, getUseCache());

public Object get(AbstractClassGenerator gen, boolean useCache) {
	// Check whether caching is enabled
	if(! useCache) {// Generate bytecode
		return gen.generate(ClassLoaderData.this);
	}
	else {
		Object cachedValue = generatedClasses.get(gen);
		returngen.unwrapCachedValue(cachedValue); }}Copy the code

To generate (ClassLoaderData. This);

protected Class generate(ClassLoaderData data) {
	Class gen;
	Object save = CURRENT.get();
	CURRENT.set(this);
	try {
		ClassLoader classLoader = data.getClassLoader();
		if (classLoader == null) {
			throw new IllegalStateException("ClassLoader is null while trying to define class " +
					getClassName() + ". It seems that the loader has been expired from a weak reference somehow. " +
					"Please file an issue at cglib's issue tracker.");
		}
		synchronized (classLoader) {
			// Generate the proxy class name
			String name = generateClassName(data.getUniqueNamePredicate());
			data.reserveName(name);
			this.setClassName(name);
		}
		// Use the class loader to try loading, if not, start creating bytecode
		if (attemptLoad) {
			try {
				gen = classLoader.loadClass(getClassName());
				return gen;
			}
			catch (ClassNotFoundException e) {
				// ignore}}// Generate bytecode with the specified policy
		byte[] b = strategy.generate(this);
		String className = ClassNameReader.getClassName(new ClassReader(b));
		ProtectionDomain protectionDomain = getProtectionDomain();
		synchronized (classLoader) { // just in case
			// Load the bytecode into the JVm to generate the proxy class
			gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain, contextClass);
			// SPRING PATCH END
		}
		return gen;
	}
	catch (RuntimeException | Error ex) {
		throw ex;
	}
	catch (Exception ex) {
		throw new CodeGenerationException(ex);
	}
	finally{ CURRENT.set(save); }}Copy the code

Enter the Generate () method for DefaultGeneratorStrategy

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

ToByteArray () in DebuggingClassWriter

public byte[] toByteArray() {
     return (byte[]) ((byte[])AccessController.doPrivileged(new PrivilegedAction() {
         public Object run(a) {
             byte[] b = ((ClassWriter)DebuggingClassWriter.access$001(DebuggingClassWriter.this)).toByteArray();
             if(DebuggingClassWriter.debugLocation ! =null) {
                 String dirs = DebuggingClassWriter.this.className.replace('. ', File.separatorChar);

			     // Asm generates class files
                 try{(new File(DebuggingClassWriter.debugLocation + File.separatorChar + dirs)).getParentFile().mkdirs();
                     File file = new File(new File(DebuggingClassWriter.debugLocation), dirs + ".class");
                     BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));

                     try {
                         out.write(b);
                     } finally {
                         out.close();
                     }
                     if(DebuggingClassWriter.traceCtor ! =null) {
                         file = new File(new File(DebuggingClassWriter.debugLocation), dirs + ".asm");
                         out = new BufferedOutputStream(new FileOutputStream(file));

                         try {
                             ClassReader cr = new ClassReader(b);
                             PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
                             ClassVisitor tcv = (ClassVisitor)DebuggingClassWriter.traceCtor.newInstance(null, pw);
                             cr.accept(tcv, 0);
                             pw.flush();
                         } finally{ out.close(); }}}catch (Exception var17) {
                     throw newCodeGenerationException(var17); }}returnb; }})); }Copy the code

AbstractClassGenerator create() calls firstInstance((Class) obj) if the Class type is Class; Method uses reflection to generate proxy objects from the specified class; If the proxy Class generated by the create() method is not of Class type, nextInstance(obj) of the Enhancer Class is called;

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

	if (classOnly) {
		return data.generatedClass;
	}

	Class[] argumentTypes = this.argumentTypes;
	Object[] arguments = this.arguments;
	if (argumentTypes == null) {
		argumentTypes = Constants.EMPTY_CLASS_ARRAY;
		arguments = null;
	}
	// Create a proxy object instance
	return data.newInstance(argumentTypes, arguments, callbacks);
}
Copy the code

Enter newInstance(argumentTypes, arguments, callbacks); Parameters as follows:

  • An array of constructor parameter types for the proxy object
  • An array of construct parameters for the proxy object
  • Callback object array
public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {
	// Set the thread callback
	setThreadCallbacks(callbacks);
	try {
		// Explicit reference equality is added here just in case Arrays.equals does not have one
		if (primaryConstructorArgTypes == argumentTypes ||
				Arrays.equals(primaryConstructorArgTypes, argumentTypes)) {
			// Generate proxy objects through reflection mechanism
			return ReflectUtils.newInstance(primaryConstructor, arguments);
		}
		return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments);
	}
	finally {
		setThreadCallbacks(null); }}Copy the code

Enter the newInstance method of reflection

public static Object newInstance(final Constructor cstruct, final Object[] args) {
	// Get the security access id
	boolean flag = cstruct.isAccessible();
	try {
		if(! flag) { cstruct.setAccessible(true);
		}
		// reflection creates proxy object instances
		Object result = cstruct.newInstance(args);
		return result;
	}
	// cath ......
	finally {
		if(! flag) { cstruct.setAccessible(flag); }}}Copy the code

Bytecode file

Proxy class bytecode files

Put the UserService in it

EnhancerByCGLIBEnhancerByCGLIB

public class UserService$$EnhancerByCGLIB$$4d8d88b5 extends UserService implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$login$0$Method;
    private static final MethodProxy CGLIB$login$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;

	// static code block that reflects the create proxy method
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.dmsd.proxy.cglib.UserService$$EnhancerByCGLIB$$4d8d88b5");
        Class var1;
        CGLIB$login$0$Method = ReflectUtils.findMethods(new String[]{"login"."()V"}, (var1 = Class.forName("com.dmsd.proxy.cglib.UserService")).getDeclaredMethods())[0];
        CGLIB$login$0$Proxy = MethodProxy.create(var1, var0, "()V"."login"."CGLIB$login$0");
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals"."(Ljava/lang/Object;) Z"."toString"."()Ljava/lang/String;"."hashCode"."()I"."clone"."()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;) Z"."equals"."CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;"."toString"."CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I"."hashCode"."CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;"."clone"."CGLIB$clone$4");
    }

    // Execute methodProxy.invokeSuper(o, objects); Called when
    final void CGLIB$login$0() {
        super.login();
    }
   	// Core method
    public final void login(a) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if(var10000 ! =null) {
        	// Call the interceptor
            var10000.intercept(this, CGLIB$login$0$Method, CGLIB$emptyArgs, CGLIB$login$0$Proxy);
        } else {
            super.login(); }}final boolean CGLIB$equals$1(Object var1) {
        return super.equals(var1);
    }

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

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

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

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

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

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

        if(var10000 ! =null) {
            Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);
            return var1 == null ? 0 : ((Number)var1).intValue();
        } else {
            return super.hashCode(); }}final Object CGLIB$clone$4(a)throws CloneNotSupportedException {
        return super.clone();
    }

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

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

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

    public UserService$$EnhancerByCGLIB$$4d8d88b5() {
        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) {
        UserService$$EnhancerByCGLIB$$4d8d88b5 var1 = (UserService$$EnhancerByCGLIB$$4d8d88b5)var0;
        if(! var1.CGLIB$BOUND) { var1.CGLIB$BOUND =true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }

            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0]; }}public Object newInstance(Callback[] var1) {
        CGLIB$SET_THREAD_CALLBACKS(var1);
        UserService$$EnhancerByCGLIB$$4d8d88b5 var10000 = new UserService$$EnhancerByCGLIB$$4d8d88b5();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

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

    public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
        CGLIB$SET_THREAD_CALLBACKS(var3);
        UserService$$EnhancerByCGLIB$$4d8d88b5 var10000 = new UserService$$EnhancerByCGLIB$$4d8d88b5;
        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(); }}Copy the code

The static code block above initializes the proxy class when the BYtecode file is loaded by the JVM class, primarily to create proxy methods.

From the generated decompiled source code for the proxy class, we can see:

  • The proxy class inherits from the target class UserService
  • The interceptor calls Intercept (). Since MyIntercepter is the implementation of the custom MethodInterceptor, we directly call the Intercept () method overridden in our custom class, thus completing the dynamic proxy implementation from the proxy object to the target object.
Index class bytecode files

Cglib does not use reflection in method calls. Instead, it assigns indexes to each proxy class method to directly search for specific methods. Similar to method calls, this is much more efficient than reflection calls through local methods.

UserService

EnhancerByCGLIBEnhancerByCGLIB

FastClassByCGLIBFastClassByCGLIB

public class UserService$$EnhancerByCGLIB$$4d8d88b5$$FastClassByCGLIB$$aaec18ff extends FastClass {
    public UserService$$EnhancerByCGLIB$$4d8d88b5$$FastClassByCGLIB$$aaec18ff(Class var1) {
        super(var1);
    }
   public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch(var10000.hashCode()) {
        case -2035807581:
            if (var10000.equals("CGLIB$login$0()V")) {
                return 19;
            }
            break;
        case -1882565338:
            if (var10000.equals("CGLIB$equals$1(Ljava/lang/Object;) Z")) {
                return 18;
            }
            break;
        case -1870561232:
            if (var10000.equals("CGLIB$findMethodProxy(Lorg/springframework/cglib/core/Signature;) Lorg/springframework/cglib/proxy/MethodProxy;")) {
                return 13;
            }
            break; . }return -1; }}Copy the code

Let’s see what the implementation looks like.

1. The client invokes the target method using the proxy object

UserService proxyUserService = (UserService) enhancer.create();
proxyUserService.login();
Copy the code

At this point, the JVM calls UserService, the bytecode file that generates the proxy class

EnhancerByCGLIBEnhancerByCGLIB

public final void login(a) {
   MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
        CGLIB$BIND_CALLBACKS(this);
        var10000 = this.CGLIB$CALLBACK_0;
    }

    if(var10000 ! =null) {
    	// call the interceptor
        var10000.intercept(this, CGLIB$login$0$Method, CGLIB$emptyArgs, CGLIB$login$0$Proxy);
    } else {
        super.login(); }}Copy the code

2. Invoke interceptors

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    System.out.println("cglib proxy before...");
    Object obj = methodProxy.invokeSuper(o, objects);
    System.out.println("cglib proxy after...");
    return obj;
}
Copy the code

Follow up invokeSuper (o, objects)

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

3. Find the corresponding index of the method

Init () above does the method index binding, calling a method with an index of 16

Invoke (fCI.i2, obj, args); invoke(fCI.i2, obj, args)

Among them:

  • Fci: FastClassInfo, an internal static class for MethodProxy that encapsulates two index files
  • F2: The file index class of the proxy class
  • I2: indicates the specific method index

fci.f2.invoke(fci.i2, obj, args); Index 16 is used to call the method in the index file.

case 16:
      var10000.CGLIB$login$0(a);return null;
Copy the code

4. Implement the target method

Find the method in the proxy class corresponding to index 16, the method of the target class.

final void CGLIB$login$0() {
    super.login();
}
Copy the code

JDK vs. CGLIB

JDK

Features:

  • The target class must implement the interface
  • Use reflection to generate proxy objects

CGLIB

CGLIB agent execution: call target method – > execute target method callback: interceptor – > find index of target method – > find index of target method and execute

Features:

  • The target class does not need to implement the interface, but cannot be modified with final
  • CGLIB calls target methods by indexing (similar to method calls), which is more efficient than reflection calls

Earlier versions of the JDK were far less efficient than CGLIB, but as the JDK has been iteratively optimized, Spring’s CGLIB has stagnated, and you can see that newer JDK proxies (JDK7 and later) are more efficient overall than CGLIB proxies. But CGLIB’s performance in calling target methods is better than that of the JDK using reflection.