1. Introduction

Provide a proxy for other objects to control access to an object. The proxy class is mainly responsible for preprocessing messages for the delegate (real objects), filtering messages, and passing messages to the delegate class. The proxy class does not realize the concrete service, but uses the delegate class to complete the service, and encapsulates the execution result.

2. Classification of agents

There are three types of proxy: static proxy, JDK dynamic proxy and Cglib dynamic proxy

2.1 Static Proxy

A simple example of a static proxy can start by creating an interface where the methods to be written are our original business.

public interface WorkInterface {
    void doWork(a);
}
Copy the code

Proxied classes need to implement this interface! And define your own business.

public class Worker implements WorkInterface{
    @Override
    public void doWork(a) {
        System.out.println("Business done!"); }}Copy the code

Similarly, our static proxy class also needs to implement this interface! We also call our original woker method in our doWork() method and add the new business processing we added in the proxy.

public class WorkerProxy implements WorkInterface{
    private WorkInterface worker = new Worker();
    @Override
    public void doWork(a) {
        System.out.println("Before invoke doWork" );
        worker.doWork();
        System.out.println("After invoke doWork"); }}Copy the code

Obviously, when we call our static proxy class in a method:

public static void main(String[] args) {
    WorkerProxy workerProxy = new WorkerProxy();
    workerProxy.doWork();
}
/** Before invoke doWork After invoke doWork */
Copy the code

We have implemented the proxy to the doWork() method of the proxy object, and in the method to achieve the original business process of data preprocessing and result reprocessing and other functions!

The disadvantage of static proxy is that because the proxy can only serve one class, if many classes need to be proxy, then you need to write a large number of proxy classes, which is tedious.

2.2 JDK Dynamic Proxy

2.2.1 Simple use

For the dynamic proxy example, we will still use the example ~ from the previous section

public interface WorkInterface {
    void doWork(a);
}
public class Worker implements WorkInterface{
    @Override
    public void doWork(a) {
        System.out.println("Business done!"); }}Copy the code

In order to implement a dynamic proxy method that can serve all classes, we need to implement a method that lets us pass in the object we want to proxy and returns us a proxy object, which can be implemented through InvocationHandler!

We create our own ProxyHandler class and override the invoke() method inside, where we can define some additional processing of the original business!

public class ProxyHandler implements InvocationHandler {
    private Object object;
    public ProxyHandler(Object object){
        this.object = object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Add some extra processing before and after the actual business
        System.out.println("Before invoke " + method.getName());
        method.invoke(object,args);
        System.out.println("After invoke " + method.getName());
        return null; }}Copy the code

Now that we have this method, we can start:

public class ProxyTest {
    public static void main(String[] args) {
        // The first step is to have the target object that we need to delegate
        WorkInterface worker = new Worker();
        // The second step is to create our own handler. The only thing that is useful is the invoke method in this handler (i.e. the new business logic that we define ourselves).
        InvocationHandler handler = new ProxyHandler(worker);
        // Step 3: Create a Proxy object using ProxyWorkInterface workerProxy = (WorkInterface) Proxy .newProxyInstance(worker.getClass().getClassLoader(), worker.getClass().getInterfaces(), handler); workerProxy.doWork(); }}/* Output: Before invoke doWork After invoke doWork */
Copy the code

As you can see, when the proxy class finally executed the method with the same name as our target business, it did the corresponding business processing as we set up in the invoke() method. At the same time, we define the ProxyHandler can implement dynamic proxy for different classes according to the object passed in at creation time.

2.2.2 JDK dynamic proxy implementation

The implementation of a dynamic proxy consists of the following steps:

  1. Create your own call handler by implementing the InvocationHandler interface, as in the previous sectionProxyHandler;
  2. Create a dynamic Proxy class by specifying a ClassLoader object and a set of interfaces (in this case, just one interface) for the Proxy class;
  3. The constructor of the dynamic proxy class, whose only parameter type is the calling processor interface type, is obtained through reflection.
  4. A dynamic proxy class instance is created using a constructor that calls the handler (ProxyHandler) object is passed as a parameter.

We’ll start with the proxy.newProxyInstance () method from the previous section that creates the final Proxy object:

public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
    throws IllegalArgumentException
{	
    // Check whether the handler passed in is null, if so, throw an exception
    Objects.requireNonNull(h);
    // Verify system permissions
    finalClass<? >[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();
    if(sm ! =null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    // Generate a proxy Class object for the objectClass<? > 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);
        }
        // Get the constructor of the returned proxy class for the final construction of the proxy class object
        finalConstructor<? > cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;
        // If the Class scope is private, access is supported through setAccessible
        if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run(a) {
                    cons.setAccessible(true);
                    return null; }}); }// Get the Proxy Class constructor to create a Proxy instance.
        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 is used to generate the proxy class:

private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    return proxyClassCache.get(loader, interfaces);
}
Copy the code

Interestingly, he first performed a parameter length validation, the data size is very familiar to 65535. This is because utF-8 encoded Unicode strings are represented as CONSTANT_Utf8 in the JVM’s constant pool, and the data stored in utF-8 is an unsigned 16-bit integer, so the theoretical maximum storage length is 2^16=65536. However, since the null value is represented by two bytes, the stored data cannot be larger than 65535 bytes.

PS: This leads to an interesting string-related trivia: What is the maximum length of a String?

We all know that the underlying implementation of String is an array of char[], and that the length of an array is integer. MAX, which is the maximum length of a String. In fact, this is the maximum value a string can have at run time, and at compile time, literal strings can’t exceed 65535 because of JVM constant storage limits.

public class StringNumTest {
    public static void main(String[] args) {
        StringBuffer buffer = new StringBuffer();
        // Add 65536 a to buffer
        for (int i = 0; i < 65536; i++) {
            buffer.append('a');
        }
        // Assigning to a string s does not generate an error
        String s = buffer.toString();
        System.out.println(s);
        // However, when we use literals to assign values
        String s1 = "a... a"; // There are 65536 a's in there
        // The string length is too long!}}Copy the code

After verifying the length of the interface, we call proxyClassCache. Get () to create the interface. In short, this method checks the proxyClassCache to see if there is already a copy of the interface that we need to delegate, and returns if there is. Otherwise, create one using ProxyClassFactory.

public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);
    // Clean up expired cache entities
    expungeStaleEntries();
    // Create weakCache for this key
    Object cacheKey = CacheKey.valueOf(key, refQueue);

    // Create a key-value map for this key. In simple terms, we can understand that a key corresponds to a proxy class object that we are going to create. This key is a classloader that corresponds to the workInterface that we want to delegate
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    if (valuesMap == null) {
        ConcurrentMap<Object, Supplier<V>> oldValuesMap
            = map.putIfAbsent(cacheKey,
                              valuesMap = new ConcurrentHashMap<>());
        if(oldValuesMap ! =null) { valuesMap = oldValuesMap; }}// subKey is a weak reference to the interface we want to proide
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    Supplier<V> supplier = valuesMap.get(subKey);
    Factory factory = null;

    while (true) {
        // A loop is required because thread contention can fail, so keep trying until the dynamic proxy is created
        if(supplier ! =null) {
            // In this method, based on the previous weak references to classLoader and interface, the proxy class is created by ProxyClassFactory, and assigned to value and returned
            V value = supplier.get();
            if(value ! =null) {
                returnvalue; }}if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }

        if (supplier == null) {
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) { supplier = factory; }}else {
            if (valuesMap.replace(subKey, supplier, factory)) {
                supplier = factory;
            } else{ supplier = valuesMap.get(subKey); }}}}Copy the code

In the supplene.get () method, the proxyClassfactory.apply () method is called to return the proxy class for the given ClassLoader and interfaces:

publicClass<? > 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 and gets the same object as the one passed inClass<? > 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 result is an interface
        if(! interfaceClass.isInterface()) {throw new IllegalArgumentException(
                interfaceClass.getName() + " is not an interface");
        }
        // Verify that duplicate interface records exist
        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;

    // Check whether the proxy target interface is public
    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"); }}}//proxyPkg is the generation path of the target proxy class
    if (proxyPkg == null) {
        // if no non-public proxy interfaces, use com.sun.proxy package
        proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
    }
    // The proxy class name
    long num = nextUniqueNumber.getAndIncrement();
    String proxyName = proxyPkg + proxyClassNamePrefix + num;

    // Here it is! Generated bytecode for our proxy class!
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        proxyName, interfaces, accessFlags);
    try {
        // Return the proxy class we generated!
        return defineClass0(loader, proxyName,
                            proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
        throw newIllegalArgumentException(e.toString()); }}}Copy the code

In ProxyGenerator. GenerateProxyClass (), generates a proxy class bytecode!

public static byte[] generateProxyClass(finalString var0, Class<? >[] var1,int var2) {
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    // The actual bytecode generation method is here ~
    final byte[] var4 = var3.generateClassFile();
    // You can choose whether to save the generated dynamic proxy file to the local computer after execution
    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");
                    }

                    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

The actual bytecode generation method is var3.GenerateclassFile (), as follows:

private byte[] generateClassFile() {
    // the first step is to add the interface method and Object method to the proxyMethod (proxyMethod)
    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
    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();

    List var12;
    while(var11.hasNext()) {
        var12 = (List)var11.next();
        checkReturnTypes(var12);
    }

    Iterator var15;
    try {
        // Generate the constructor to be propped
        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();
                this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;".10));
                this.methods.add(var16.generateMethod()); }}this.methods.add(this.generateStaticInitializer());
    } catch (IOException var10) {
        throw new InternalError("unexpected I/O Exception", var10);
    }

    if (this.methods.size() > 65535) {
        throw new IllegalArgumentException("method limit exceeded");
    } else if (this.fields.size() > 65535) {
        throw new IllegalArgumentException("field limit exceeded");
    } else {
        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); }}}Copy the code

The resulting proxy class looks like this: =.= =

public final class $Proxy0 extends Proxy implements WorkInterface {
    // All methods are derived from reflection. Look at the static block at the end of the code
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
	
    // The invoke() constructor passes in a handler in which we override the invoke() method to define our logic
    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 doWork(a) throws  {
        try {
            // When we construct the Proxy class, we pass our own handler into the class as an internal object of the parent class, so we call h (handler) with super. Invoke the invoke method (your own new business logic) and execute the doWork method of the proxy class!
            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); }}// Use reflection to get the corresponding method from the class name and method name!
    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.emoli.test.service.WorkInterface").getMethod("doWork");
            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

2.2.3 JDK Dynamic Proxy Summary

Based on the above source code analysis, we can draw some key points about JDK dynamic proxies:

  • To customize the logic to be implemented by the proxy class, we need to create our own inheritanceInvocationHandlerHandler, and rewrite the insideinvoke()Methods;
  • As you can see from the generated proxy class code, it is aProxyClass, and needs to implement the interface of our original method, in this caseWorkInterface;
  • The JDK dynamic proxy class and the original class must inherit from the same target interface, and the business content of the proxy also resides in the target interface methods.

2.3 Cglib Dynamic proxy

2.3.1 Simple use

We also use a simple example to look at the use of cglib dynamic proxies. We create a Student class with a method called doStudy().

public class Student {

    public void doStudy(a){
        System.out.println("Start studying!"); }}Copy the code

Similar to JDK dynamic proxies, we need to define a method interceptor in Cglib dynamic proxies:

public class StudyInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("before study");
        Object object = methodProxy.invokeSuper(obj, args);
        System.out.println("after study");
        returnobject; }}Copy the code

Finally, we test!

public static void main(String[] args) {
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\Users\\emoli\\IdeaProjects\\demo\\src\\main\\java\\com\\emotest\\cglib");
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Student.class);
    enhancer.setCallback(new StudyInterceptor());

    Student stu = (Student) enhancer.create();
    stu.doStudy();
}
/* Output: before study start after study */
Copy the code

As you can see, it implements the same functionality as the JDK dynamic proxy.

2.3.2 Cglib dynamic proxy implementation

As you can see, we have created an Enhancer object that is somewhat similar to the Proxy class in JDK dynamic proxies, which does all the work of creating Proxy classes and so on.

The Enhancer class inherits the AbstractClassGenerator class, whose no-argument construct uses the parent’s no-argument construct and passes in its own class name

private static final Source SOURCE = new Source(Enhancer.class.getName());

public Enhancer(a) {
    super(SOURCE);
}
Copy the code

AbstractClassGenerator constructor:

protected AbstractClassGenerator(AbstractClassGenerator.Source source) {
    // The default build policy
    this.strategy = DefaultGeneratorStrategy.INSTANCE;
    // The default naming policy
    this.namingPolicy = DefaultNamingPolicy.INSTANCE;
    DEFAULT_USE_CACHE is a Boolean value that defaults to true. This is similar to dynamic proxy in the JDK
    this.useCache = DEFAULT_USE_CACHE;
    this.source = source;
}
Copy the code

After generating the Enhancer object, we first set the parent class using the setSuperclass() method. Here we can see that the Cglib dynamic proxy is implemented based on inheriting the target class and does not require the target business method to come from an interface.

public void setSuperclass(Class superclass) {
    // Check whether it is an interface
    if(superclass ! =null && superclass.isInterface()) {
        this.setInterfaces(new Class[]{superclass});
    // Whether it is an object.class
    } else if(superclass ! =null && superclass.equals(Object.class)) {
        this.superclass = null;
    // Otherwise, assign directly to superclass
    } else {
        this.superclass = superclass; }}Copy the code

Next, we set the Enhancer callback and pass in the MethodInterceptor we defined. How to use the callback will be discussed in the following code

public void setCallback(Callback callback) {
    this.setCallbacks(new Callback[]{callback});
}

public void setCallbacks(Callback[] callbacks) {
    if(callbacks ! =null && callbacks.length == 0) {
        throw new IllegalArgumentException("Array cannot be empty");
    } else {
        this.callbacks = callbacks; }}Copy the code

The previous operations are easy to understand, but the most important operation is in enhancer.create()!

public Object create(a) {
    this.classOnly = false;
    this.argumentTypes = null;
    // createHelper() is called
    return this.createHelper();
}

private Object createHelper(a) {
    // Do a pre-check first
    this.preValidate();
    Object key = KEY_FACTORY.newInstance(
        // The inherited target class
        this.superclass ! =null ? this.superclass.getName() : null.// Null if not an interface
        ReflectUtils.getNames(this.interfaces), 
        // Filter is null if callback is not multiple
        this.filter == ALL_ZERO ? null : new WeakCacheKey(this.filter), 
        // Callback type is MethodInterceptor
        this.callbackTypes, 
        // Use factory, default is true
        this.useFactory, 
        // Defaults to true
        this.interceptDuringConstruction, 
        // Sequence version ID, null by default
        this.serialVersionUID);
    
    this.currentKey = key;
    // AbstractClassGenerator create method, passing in a key that contains the target class and some configuration for the cglib proxy
    Object result = super.create(key);
    return result;
}
Copy the code

In createHelper(), the preValidate() method is first executed:

private void preValidate(a) {
    if (this.callbackTypes == null) {
        // Verify the type of our callback class. In this case, the type is MethodInterceptor;
        this.callbackTypes = CallbackInfo.determineTypes(this.callbacks, false);
        // A valid callback type ~
        this.validateCallbackTypes = true;
    }

    if (this.filter == null) {
        // Determine if there are multiple callbacks, and if so, define a filter
        if (this.callbackTypes.length > 1) {
            throw new IllegalStateException("Multiple callback types possible but no filter specified");
        }
		// If there are no more than one callback, filter is set to the default null value
        this.filter = ALL_ZERO; }}Copy the code

We then build a key with key_factory.newinstance ()! KEY_FACTORY is an EnhancerKey object in the Enhancer class, which uses the keyFactory to create:

private static final EnhancerKey KEY_FACTORY =
    (EnhancerKey)KeyFactory.create(EnhancerKey.class, KeyFactory.HASH_ASM_TYPE, null);
Copy the code

Here we pass in a keyfactory.hash_ASm_type, which is the policy for creating hashCode:

public static final HashCodeCustomizer HASH_ASM_TYPE = new HashCodeCustomizer() {
    public boolean customize(CodeEmitter e, Type type) {
        if (Constants.TYPE_TYPE.equals(type)) {
            e.invoke_virtual(type, GET_SORT);
            return true;
        }
        return false; }};Copy the code

Keyfactory.create () is called to build a KEY_FACTORY:

public static KeyFactory create(Class keyInterface, KeyFactoryCustomizer first, List<KeyFactoryCustomizer> next) {
    return create(keyInterface.getClassLoader(), keyInterface, first, next);
}

public static KeyFactory create(ClassLoader loader, Class keyInterface, KeyFactoryCustomizer customizer, List
       
         next)
        {
    The Generator class is an inner class of KeyFactory that also inherits AbstractClassGenerator
    Generator gen = new Generator();
    // Set the interface to EnhancerKey
    gen.setInterface(keyInterface);
	// Bind the customizer
    if(customizer ! =null) {
        gen.addCustomizer(customizer);
    }
    if(next ! =null && !next.isEmpty()) {
        for(KeyFactoryCustomizer keyFactoryCustomizer : next) { gen.addCustomizer(keyFactoryCustomizer); }}// Set the class loader
    gen.setClassLoader(loader);
    // Create a KEY_FACTORY and return it
    return gen.create();
}
Copy the code

Create KEY_FACTORY with gen.create() :

public KeyFactory create(a) {
    // Set the name prefix EnhancerKey
    setNamePrefix(keyInterface.getName());
    // Finally, the gen calls the create method of the parent class AbstractClassGenerator
    return (KeyFactory)super.create(keyInterface.getName());
}
Copy the code

AbstractClassGenerator the AbstractClassGenerator create() method is called several times in proxy generation.

protected Object create(Object key) {
        try {
            Since we passed the target proxy class as superclass, the classLoader here uses the same AppClassloader as our proxy class
            ClassLoader loader = getClassLoader();
            // Define the cache
            Map<ClassLoader, ClassLoaderData> cache = CACHE;
            // See if there is any classloader data in the cache
            ClassLoaderData data = cache.get(loader);
            if (data == null) {
                synchronized (AbstractClassGenerator.class) {
                    // There is a lock here, because of the multithreading, so the second check
                    cache = CACHE;
                    data = cache.get(loader);
                    // If not, new one and update the Cache
                    if (data == null) {
                        Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
                        // What is a ClassLoaderData object?
                        data = newClassLoaderData(loader); newCache.put(loader, data); CACHE = newCache; }}}// Key is the EnhancerKey name
            this.key = key;
            // Call the get method of ClassLoaderData to get the Class instance from the cache or return the Class instance through the bytecode file generated by ASM.
            Object obj = data.get(this, getUseCache());
            if (obj instanceof Class) {
                // Create an instance!
                return firstInstance((Class) obj);
            }
            return nextInstance(obj);
        } catch (RuntimeException e) {
            throw e;
        } catch (Error e) {
            throw e;
        } catch (Exception e) {
            throw newCodeGenerationException(e); }}Copy the code

Line 18 of the above code builds a new ClassLoaderData object. Let’s look at its constructor:

public ClassLoaderData(ClassLoader classLoader) {
    if (classLoader == null) {
        throw new IllegalArgumentException("classLoader == null is not yet supported");
    }
    // A weak reference to a classloader is used to prevent memory leaks
    this.classLoader = new WeakReference<ClassLoader>(classLoader);
    // Function is an interface where the apply method is overridden
    Function<AbstractClassGenerator, Object> load =
        new Function<AbstractClassGenerator, Object>() {
        public Object apply(AbstractClassGenerator gen) {
            Class klass = gen.generate(ClassLoaderData.this);
            returngen.wrapCachedClass(klass); }}; generatedClasses =new LoadingCache<AbstractClassGenerator, Object, Object>(GET_KEY, load);
}
Copy the code

After that, the get() method of ClassLoaderData is called to retrieve the Class instance from the cache or return the Class instance through asm generation of bytecode files.

public Object get(AbstractClassGenerator gen, boolean useCache) {
    if(! useCache) {return gen.generate(ClassLoaderData.this);
    } else {
        // We use caching and generatedclasses.get () to get a virtual reference to the target class
        // In the current logic, the purpose is to create an EnhancerKey, so cachedValue is a virtual reference to the EnhancerKey
        Object cachedValue = generatedClasses.get(gen);
        // unwrapCachedValue means to unlock the virtual reference, i.e. to get the real class object to generate the instance!
        returngen.unwrapCachedValue(cachedValue); }}Copy the code

Now an instance of KEY_FACTORY of type EnhancerKey has been created!

If we go back to createHelper(), we can see that we have generated a key and passed it into the create() method of the enhancer parent class by calling super.create(key). In fact, AbstractClassGenerator’s Create () method is used. The key we pass in contains the fully qualified name of our target class, the method callback we pass in, and other information that constructs a class. Finally, it returns a proxy class that inherits from the target class.

So far, AbstractClassGenerator is clear. It builds a Cglib proxy class that inherits from our target class based on the information we pass in, generates the bytecode file of the proxy class inside, and returns an instantiation object.

Let’s take a look at the core of the generated bytecode, the method sayHello() in our target class overridden by the Cglib proxy class:

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

public final void sayHello(a) {
  Var10000 is our definition of MethodInterperceptor
  MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
  if (var10000 == null) {
    CGLIB$BIND_CALLBACKS(this);
    var10000 = this.CGLIB$CALLBACK_0;
  }
  if(var10000 ! =null) {
    // Call the intercept method in the callback method, which we override
    var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
  } else {
    super.sayHello(); }}Copy the code

Let’s get our own StudyInterceptor back for a comparison

public class StudyInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {				
        Method is the original method reference in the target class. Args is the method parameter. MethodProxy is a method proxy
        System.out.println("before study");
        Object object = methodProxy.invokeSuper(obj, args);
        System.out.println("after study");
        returnobject; }}Copy the code

Key: look! We remove the logic we added ourselves, and the actual execution of the original target method is done with methodProxy:

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
  try {
    // Index the methods of the target class
    init();
    FastClassInfo fci = fastClassInfo;
    // Calling a method with index i2 in the proxy class actually calls the original method of our target class.
    return fci.f2.invoke(fci.i2, obj, args);
  } catch (InvocationTargetException e) {
    throwe.getTargetException(); }}Copy the code

This class, in the MethodInterceptor, init() and creates a reference index for all methods, so that we can access the corresponding subscript from an array of references, avoiding the performance cost of reflection based methods!

2.3.3 Cglib Dynamic Proxy Summary

Based on the above source code analysis, we can draw the following key points:

  • Cglib dynamic proxies are implemented by inheritance, which does not require the target class to inherit from an interface.
  • In the process of proxy, Cglib does not use reflection for method acquisition, but FastClass for fast target method acquisition, which improves performance.
  • Since it is implemented based on inheritance, the drawback of Cglib is also obvious. It cannot be used as a proxyFinalClasses and methods modified by keywords.