The proxy pattern

The proxy mode includes static proxy and dynamic proxy

Static proxies are simple and easy to understand, but they are not often used in practice for the following reasons:

  • If you need to proxy multiple classes, the interface of the proxy object and the target object is the same. If you maintain only one proxy class, the class needs to implement multiple interfaces, resulting in a large proxy class
  • If the functions of a proxy class need to be extended or modified, you need to modify both the proxy class and the target object, which is difficult to maintain

Dynamic proxies have a wide range of applications, such as Spring AOP, RPC remote calls, Java annotation object acquisition, logging, user authentication, global exception handling, and performance monitoring…

So let’s take a look at two dynamic proxies that are common in Java: JDK native dynamic proxies and CGLIB dynamic proxies

1 Dynamic Proxy

Dynamic proxy is a structural design pattern that provides a proxy object to an object and controls the access to the real object

The source code for a dynamic proxy is generated dynamically by the JVM during the program's run using a mechanism such as reflection, which means that there are no bytecode files for the proxy class before it is run

1.1 Proxy Mode Involves roles

  • Subject(Abstract Subject role)

    Public external methods that define proxy classes and real topics are also methods that proxy classes proxy real topics

  • RealSubject(RealSubject role)

    Classes that actually implement business logic

  • Proxy(Proxy subject role)

    Used to proxy and encapsulate real topics

1.2 Dynamic generation principle

We know that the JVM class loading process is divided into five phases: load, validate, prepare, parse, and initialize. There are three things that need to be done during the load phase:

  • Gets the binary byte stream that defines a class by its fully qualified name
  • Convert the static storage structure represented by this byte stream to the runtime data structure of the method area
  • Generate an in-memory Class object that represents the Class and acts as a method area for the Class’s various data access points

In fact, the JVM specification is not specific about these three points, so the actual implementation is very flexible. For example, there are several ways to obtain the binary byte stream (class bytecode) of a class:

  • From the ZIP package, which is the basis for JAR, EAR, WAR, and so on
  • From the network. The typical application is an Applet
  • Runtime compute generationDynamic proxy technology is used most in this scenario; injava.lang.reflect.ProxyClass is usedProxyGenerator.generateProxyClassTo generate the form for a particular interface*$ProxyProxy class binary byte stream
  • Generated by other files, the typical application is JSP, which generates the corresponding Class Class from JSP files
  • Get it from the database and so on

This leads us to the conclusion that dynamic proxy is calculated bytecode of the proxy class, based on the interface or target object, and then loaded into the JVM for use

Of course, this raises some questions: How do you do the calculation? How do I generate bytecode files? The situation was more complicated than we thought so we went straight to the existing plan

In order to keep the generated proxy class consistent with the target object (real subject role), there are two more common ways:

  • Dynamic proxy by implementing the interface ->JDK
  • By inheriting classes ->CGLIB dynamic proxy

1.3 JDK Dynamic Proxy

The JDK dynamic Proxy involves two main categories: Java. Lang. Reflect. The Proxy and Java lang. Reflect. InvocationHandler

1.3.1 Testing the JDK dynamic Proxy

The following is a preliminary understanding of a case:

Write a call logic handler LogHandler class to provide logging enhancements and implement the InvocationHandler interface to maintain a target object in the LogHandler class that is the propeted object (real subject role)

By the proxy class

public interface UserService {
    void select(a);

    void update(a);
}

public class UserServiceImpl implements UserService{
    @Override
    public void select(a) {
        System.out.println("select by id");
    }

    @Override
    public void update(a) {
        System.out.println("update by id"); }}Copy the code

Log enhancer

@Slf4j
public class LogHandler implements InvocationHandler {

    private Object target;

    public LogHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        doBefore();
        // target calls the target method
        Object res = method.invoke(target, args);
        doAfter();
        return res;
    }

    /** * before the target method is executed */
    private void doBefore(a) {
        log.info("log start time:{}".new Date());
    }

    /** * after the target method is executed */
    private void doAfter(a) {
        log.info("log end time:{}".newDate()); }}Copy the code

The method argument in the invoke() method is actually the method object executed by the proxy object, and the args argument is the parameter value passed in when the method is executed; Proxy is actually the generated proxy object instance

Since the target attribute maintained in the LogHandler class is the propeted object, method.invoke(target, args) is invoked by reflection to get the result of the propeted object calling the corresponding method

The client

public class Client {
    public static void main(String[] args) {
        / / set the dynamically generated class is saved as a file System. The getProperties () setProperty (" sun. Misc. ProxyGenerator. SaveGeneratedFiles ", "true");
        // Create a proxied object
        UserServiceImpl userService = new UserServiceImpl();
        // Get the class loader
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Get all interface classesClass<? >[] interfaces = userService.getClass().getInterfaces();// Create a call request handler to be passed to the proxy class to handle all method calls made by the proxy object
        LogHandler logHandler = new LogHandler(userService);
        /** * When creating a proxy object based on the information provided above: The JDK dynamically creates bytecodes in memory that correspond to the.class file using the passed parameter information, converts the bytecodes to the corresponding class * 3, and then calls newInstance() to create a proxy instance */
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
        // Invoke the proxy methodproxy.select(); proxy.update(); }}Copy the code

The logHandler generates the proxy instance by specifying the class loader (which needs to load the generated bytecode file), all implemented interfaces (so that the generated proxy class has the same actions as the proxied class) through the Proxy#newProxyInstance method

The test results

summary

For Java. Lang. Reflect. InvocationHandler (call processor)

The Object invoke(Object Proxy, Method Method, Object[] args) Method defines the action that the proxy Object wants to perform when calling the Method of the proxy Object

For Java. Lang. Reflect the Proxy

static Object newProxyInstance(ClassLoader loader, Class
[] interfaces, InvocationHandler h) method constructs a new instance of a proxy class that implements the specified interface and calls methods using the specified logic of the calling handler

1.3.2 Invocation process of proxy class

We know that the proxy class object is dynamically generated, so what does the generated proxy class look like? Let’s introduce a utility class to hold the proxy class

Utility class

public class ProxyUtil {
    /** * Bytecode dynamically generated based on class information is saved to disk, default in clazz directory * params: clazz needs to generate dynamic proxy class * proxyName: for the name of the dynamically generated proxy class */
    public static void generateClassFile(Class clazz,String proxyName) {
        // Generate bytecode based on the class information and the provided proxy class name
        byte[] bytes = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        String path = clazz.getResource(".").getPath();
        System.out.println(path);
        FileOutputStream fos = null;
        try {
            // Save to hard disk
            fos = new FileOutputStream(path + proxyName + ".class");
            fos.write(bytes);
            fos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                assertfos ! =null;
                fos.close();
            } catch(IOException e) { e.printStackTrace(); }}}}Copy the code

The first step is to get the proxy bytecode of class clazz via ProxyGenerator#generateProxyClass

Then get the root path of the project, and finally save it to a local file via the IO stream

Modify client – Generate proxy file

@Slf4j
public class Client {
    public static void main(String[] args) {.../** * When creating a proxy object based on the information provided above: The JDK dynamically creates bytecodes in memory that correspond to the.class file using the passed parameter information, converts the bytecodes to the corresponding class * 3, and then calls newInstance() to create a proxy instance */
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
        System.out.println(proxy);
        log.info("{}",proxy);
        // Invoke the proxy method
        proxy.select();
        proxy.update();

        ProxyUtil.generateClassFile(userService.getClass(),"UserServiceProxy"); }}Copy the code

Compared with the previous generation of proxy class files mainly through utility classes

The test results

public final class UserServiceProxy extends Proxy implements UserService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    private static Method m4;

    public UserServiceProxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {... }public final String toString(a) throws  {... }public final void select(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  {... }public final void update(a) throws  {
        try {
            super.h.invoke(this, m4, (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("org.jiang.proxy.jdkproxy.UserService").getMethod("select");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m4 = Class.forName("org.jiang.proxy.jdkproxy.UserService").getMethod("update");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

From UserServiceProxy, you can find:

  • UserServiceProxy inherits the Proxy class, implements all the propped interfaces, and overrides methods like equals, hashCode, and toString

  • Since UserServiceProxy inherits the Proxy class, each Proxy class is associated with an InvocationHandler method call handler (because the Proxy class maintains a handler object internally)

  • Class and all methods are finial qualified, so proxy classes can only be used, not inherited

  • Each Method is described by a Method object, which is created in a static block of code and named in the m+ number format

  • Method is called with super.hinvoke (this, m3, (Object[])null), where his actually the method handler, So H.invoke () is actually the InvocationHandler object we pass in with Proxy#newProxyInstance to make the actual processing logic call

conclusion

In fact, during the generation of Proxy class, all methods of the Proxy class are converted into Method objects. Since Proxy class inherits from Proxy class, it is equivalent to maintaining an InvocationHandler object inside Proxy class. This object is passed in when we call Proxy#newInstance

In turn, when a method of the propped class is called, it is essentially converted to invoke the specified Mthod by calling the invoke() method through the InvocationHandler object

1.3.3 JDK agent source code analysis

The following is an analysis of the small case debug analyzed above:


First we locate the Proxy#newProxyInstance method to generate the proxy object:

After these two steps, let’s first look at the class object of the proxy object:

The proxy class name is com.sun.proxy.$Proxy0. This class is dynamically generated by the JVM runtime and starts with $, proxy is in the middle, and the last ordinal indicates the ordinal number of the class object

And there is a common constructor:

Public com. Sun. Proxy. $Proxy0 (Java. Lang. Reflect the InvocationHandler), you can see a proxy object is through the constructor generated, and the constructor of the incoming parameters is called the processor


The Proxy#newProxyInstance method is analyzed below

    /** parameter types of a proxy class constructor */
    private static finalClass<? >[] constructorParams = { InvocationHandler.class };@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);  //h cannot be empty, so InvocationHandler is required

        finalClass<? >[] intfs = interfaces.clone();// Object copy
        final SecurityManager sm = System.getSecurityManager();
        if(sm ! =null) {  // Verify that you have permission to create a proxy class
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        * * Look up or generate the designated proxy class. */Class<? > cl = getProxyClass0(loader, intfs);/* Invoke its constructor with the designated Invocation Handler. * the designated Invocation Handler object is passed in
        try {
            if(sm ! =null) {  // Check permissions
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            // Get argument is a constructor of type InvocationHander. Class
            finalConstructor<? > cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;
            If the constructor is not public, change the modifier to public.
            if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run(a) {
                        cons.setAccessible(true);
                        return null; }}); }// Call the constructor, generate the object of the proxy class and return it
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw newInternalError(t.toString(), t); }}catch (NoSuchMethodException e) {
            throw newInternalError(e.toString(), e); }}Copy the code

Verify that the calling handler is null and copy all interfaces to intFS variables via the object

Class
cl = getProxyClass0(loader, intfs) finds or generates proxy classes, which will be analyzed in 1.3.3.1

Start execution by assigning the variable constructorParams to the class object of InvocationHandler, which is where reflection creates a proxy class with a parameter of type InvocationHandler

4. Set the access to the constructor, and pass h(the object we passed through proxy.newinstance ()) into the constructor to create a real proxy-class object and return it


1.3.3.1 Proxy# getProxyClass0
private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {/** * a cache of proxy classes */
    private static finalWeakCache<ClassLoader, Class<? >[], Class<? >> proxyClassCache =new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
    
    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

Start by checking if the interface implemented by the proxy class exceeds 65535 and throw an exception if it does

Then call proxyClassCache. Get () to generate the proxy class.

  • If the proxy class already has a specified classloader and inherits these interfaces, the generated proxy class is returned directly
  • Otherwise the proxy class is generated by ProxyClassFactory

Let’s take a look at the WeakCache#get method:


We know from above that the core of creating a class is in the ProxyClassFactory class

    /** * 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<? >>{
        // prefix for all proxy class names 
        // (all generated Proxy class prefixes are $Proxy)
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names
        // (proxy class generation sequence)
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        publicClass<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet =new IdentityHashMap<>(interfaces.length);

            // Check the interface array:
            // 1) Whether the interface can be found, and whether the loaded instance is the same as the interface instance passed in
            // 2) Verify that the loaded instance is an interface
            // 3) Interface deduplication
            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");
                }
                /* * Verify that the Class object actually represents an * interface. */
                if(! interfaceClass.isInterface()) {throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /* * Verify that this interface is not a duplicate. */
                if(interfaceSet.put(interfaceClass, Boolean.TRUE) ! =null) {
                    throw new IllegalArgumentException(
                        "repeated interface: "+ interfaceClass.getName()); }}// Declare the package path where the proxy class resides
            String proxyPkg = null;     // package to define proxy class in
            // Access modifier for the proxy class
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /* * Record the non-public interface packet path, Record the package of a non-public proxy interface so that the * proxy class will be defined in the  same package. Verify that * all non-public proxy interfaces are in the same package. */
            for(Class<? > intf : interfaces) {int flags = intf.getModifiers();
                if(! Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName();int n = name.lastIndexOf('. ');
                    String pkg = ((n == -1)?"" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if(! pkg.equals(proxyPkg)) {throw new IllegalArgumentException(
                            "non-public interfaces from different packages"); }}}if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /* * Choose a name for the proxy class to generate
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /* * Generate the specified proxy class. * Generate the bytecode of the class, and then Generate the proxy class via the local method defineClass0
            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 newIllegalArgumentException(e.toString()); }}}Copy the code

The first class declares two constants, one to represent the prefix of the generated proxy class object and the other to generate the number of the proxy class (type AtomicLong, thread-safe).

There is only one apply() method in the class, which is obviously used to generate the proxy class object, so we’ll just examine that method

  • Create a collection of IdentityHashMapmap as many interfaces as the proxy class implements

  • Iterates through all interfaces implemented by the proxy Class, looking for Class objects with the same name as the iterated element through class.forname ()

  • It then determines whether the iterated elements match the class object found through the class loader

  • If everything is fine, put the interface into the collection as key and boolea.true as value

  • Declare two variables representing the package path and access modifier of the proxy class

  • Iterate through all interfaces again to find interface elements that are not public qualified, get the package name for that element and set it to the package name of the proxy class

  • Generate the proxy class name from the prefix and ordinal (constant) of the proxy class we discussed earlier

  • Call ProxyGenerator. GenerateProxyClass () generated proxy class bytecode array

  • Generate proxy class objects using the local method defineClass0() instead

conclusion

To clarify, the ProxyClassFactory analysis is also consistent with our previous tests:

Before we write ProxyUtil tools through ProxyGenerator. GenerateProxyClass () method to generate the proxy class bytecode byte array

In the ProxyClassFactory class, this method is also used to generate the proxy class bytecode array

Then the Proxy class calls the method entry of the Proxy object by assigning the value to the InvocationHandler attribute in the Proxy class

1.3.3.2 tag

In fact, we can draw a conclusion here:

  • The process of actually generating a proxy class has nothing to do with the proxyed class, except that all interfaces of the proxyed class are passed inProxyGenerator.generateProxyClass()In the
  • This means that we can generate class objects of proxy classes by specifying some interfaces and classloaders
  • So one more thing to say when customizingInvocationHandlerWhen in the classinvoke()Methods do not necessarily involve the specific logic of the propped class (usually required), because we use the class object that generated the propped class to create a constructor using reflection and pass in the one we definedInvocationHandler
  • The generated proxy class file also makes it clear that each method in the interface actually delegates toInvocationHandlercallinvoke()methods

1.4 Cglib Dynamic proxy

We know that JDK dynamic proxies are interface-based, meaning that both the proxy class and the target class implement the same interface

The Cglib dynamic proxy is a proxy class that inherits the target class and overrides the methods of the target class to ensure that the proxy class has the same methods as the target class

Cglib inherits the target class from the proxy class. Each call to the proxy class is intercepted by the method interceptor, which is the real logic for calling the target class method

1.4.1 test additional

Import dependence

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2. 5</version>
</dependency>
Copy the code

By the proxy class

public class UserDao {
    public void select(a) {
        System.out.println("UserDao query selectById");
    }
    public void update(a) {
        System.out.println("UserDao update update"); }}Copy the code

Method interceptor (callback function)

public class CustomerLogInterceptor implements MethodInterceptor {

    / * * * *@paramO indicates the object to be enhanced *@paramMethod Method of interception *@paramObjects represents the list of arguments to the method. Primitive data types are passed in wrapper types such as int-->Integer, long-long, and double--> double *@paramMethodProxy represents a proxy for a method, and the invokeSuper method represents a method call on a prosted-object@returnThe proxy object */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        InvokeSuper executes the methods of the original class and method.invoke executes the methods of the subclass
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;
    }

    private void before(a) {
        System.out.println(String.format("log start time [%s] ".new Date()));
    }
    private void after(a) {
        System.out.println(String.format("log end time [%s] ".newDate())); }}Copy the code

The test class

public class CglibTest {
    public static void main(String[] args) {
        // Generate dynamic proxy classes in the specified directory
 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,CglibTest.class.getClassLoader().getResource(".").getPath());
        // Create a Enhance object, similar to a proxy with JDK dynamic proxies
        Enhancer enhancer = new Enhancer();
        // Set the bytecode file of the target class
        enhancer.setSuperclass(UserDao.class);
        // Set the callback function
        enhancer.setCallback(new CustomerLogInterceptor());
        // Create a proxy object
        UserDao user = (UserDao) enhancer.create();
        // method calluser.select(); }}Copy the code

The test results

This shows that not only the proxied object is proxied, but also the class file of the proxied class is generated. Let’s analyze these specific processes

1.4.2 Generating dynamic Proxy Classes

We have generated bytecode files in the specified directory above, which actually have three class files:

  • FastClass for the proxy class
  • The proxy class
  • FastClass for the target class


Proxy Class file analysis

public class UserDao$$EnhancerByCGLIB$$5c5103ea extends UserDao implements Factory {
    // Identifies whether the interceptor is already bound
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    CGLIB sets the callback to the following two variables to hold the callback class, i.e. we set all interceptors, before binding
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    // This is our interceptor, because only one CallBack has been added, so there is only one
    // CGLIB can add multiple callbacks
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    // All methods below are the original methods of the proxied class
    private static final Method CGLIB$update$0$Method;
    MethodProxy is related to the FastClass mechanism
    private static final MethodProxy CGLIB$update$0$Proxy;
    // Null argument, a default value that is passed to the interceptor when our method has no arguments
    // Do not pass null values, which is a setup idea
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$select$1$Method;
    private static final MethodProxy CGLIB$select$1$Proxy; .// This method is called in a static code block to initialize the above variable
     static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("org.jiang.proxy.cglib.UserDao$$EnhancerByCGLIB$$5c5103ea");
        Class var1;
        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$2$Method = var10000[0];
         var10000 = ReflectUtils.findMethods(new String[]{"update"."()V"."select"."()V"}, (var1 = Class.forName("org.jiang.proxy.cglib.UserDao")).getDeclaredMethods());
        CGLIB$update$0$Method = var10000[0];
        // Create MethodProxy. Each method creates a corresponding MethodProxy
        // The current proxy creates a MethodProxy for all methods of its parent class
        CGLIB$update$0$Proxy = MethodProxy.create(var1, var0, "()V"."update"."CGLIB$update$0");
        CGLIB$select$1$Method = var10000[1];
        CGLIB$select$1$Proxy = MethodProxy.create(var1, var0, "()V"."select"."CGLIB$select$1");
    }
    
    JDK dynamic proxies must have an instance of the promended class. CGLIB dynamic proxies do not need an instance of the promended class because they are inherited. So it contains all the methods of the proxied class * but when we call the update() method of the generated proxied class, we call the CGLIB proxied method * if we call the original method of the proxied class, Each primitive method in a CGLIB generated proxy class will have both types of methods */
    final void CGLIB$update$0() {
        super.update();
    }
    
    /** * the generated proxy method in which our Callback is called * when the update method of the proxy class is called, Check if there is an intercepting object that implements the MethodInterceptor interface *. If not, call the CGLIB$BIND_CALLBACKS method to get the Callback */
    public final void update(a) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if(var10000 ! =null) {
            var10000.intercept(this, CGLIB$update$0$Method, CGLIB$emptyArgs, CGLIB$update$0$Proxy);
        } else {
            super.update(); }}/** * CGLIB$BIND_CALLBACKS CGLIB$BIND_CALLBACKS CGLIB$BIND_CALLBACKS CGLIB$BIND_CALLBACKS CGLIB$BIND_CALLBACKS If it does not, the method is considered to need no proxy. How is CallBack set to CGLIB$THREAD_CALLBACKS or CGLIB$STATIC_CALLBACKS? * Intercepting objects in the Jdk dynamic proxy is passed in by the constructor when the proxy class is instantiated. * In Cglib we use Enhancers to generate the proxy class. The Enhancer firstInstance method is called to generate the proxy class instance and set the callback. 1.Enhancer: firstInstance 2.Enhancer: createUsingReflection 3. 5.Target$$EnhancerByCGLIB$$78844a0: CGLIB$SET_THREAD_CALLBACKS * This is where all callbacks are successfully set */ when CGLIB finally instantiates the proxy object
    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        UserDao$$EnhancerByCGLIB$$5c5103ea var1 = (UserDao$$EnhancerByCGLIB$$5c5103ea)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]; }}Copy the code

Because of the large amount of code here, I will simply post some key parts

It can be seen from the above:

1. The proxy class inherits from the target class. The proxy class generates two methods for every method in the target class that does not have a private or final modifier.

For the update() method, there is a final modifier overridden update method, and the CGLIB$update$0 method

The CGLIB$update$0 method actually calls the update() method of the parent class, which is the target method of the proxied class


The point here is that in the interceptor of the above case, when the method methodProxy.invokesuper (O, objects) method is called, it is the method being overridden by the proxy class, if the invoke() method is called


2. The above analysis also illustrates another problem. When we call the target method through the proxy object after generating the proxy class, we call the method overridden by the proxy class

The following is an analysis of the proxy class rewrite method (update method as an example) :

1.1 First checks if a MethodInterceptor object already exists in the class, and if not, calls the CGLIB$BIND_CALLBACKS(this) method

We cache a copy of this and determine if we can get the interceptor from ThreadLocal. If we can’t get the interceptor array from ThreadLocal, then we don’t need a proxy

1.2 After setting the interceptor, determine whether the interceptor is empty. If it is empty, directly call the target method of the proxied object; If it is not empty, the Intercept () method is called through the interceptor

1.3 We are already familiar with this, because we set up the interceptor in the Enhancer object, and it calls the intercept() method of the interceptor that we set up


It is also important to note that the proxy class has a static code block in which important methods are executed:

static {
    CGLIB$STATICHOOK1();
}

static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("org.jiang.proxy.cglib.UserDao$$EnhancerByCGLIB$$5c5103ea");
        Class var1;
    	CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;"."toString"."CGLIB$toString$3"); . }Copy the code

As we mentioned earlier, the CGLIB$STATICHOOK1() method initializes a set of variables; Calling this method in a static block of code means that the class initializes variables as soon as they are loaded

The CGLIB$STATICHOOK1() method implements methodProxy.create (), which generates corresponding MethodProxy objects for all methods of the propped class. Each method corresponding MethodProxy object in performing MethodInterceptor. Intercept () method, this method is our definition of each object interceptors to intercept method) will be passed as a parameter in the inside

Once passed in, you can use the FastClass mechanism using MethodProxy methods:

/** * A MethodProxy object that contains the names of two methods, one of which is the original method of the propped class and the other is the name of the method generated by the propped class to call the original method of the propped class. When executing methodProxy.invokesuper (), * * c1 is the class of the parent class of the proxy class * c2 is the class of the proxy class * name1 is the name of the original method of the proxy class */
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
    MethodProxy proxy = new MethodProxy();
    // Signature is used to identify a unique method
    // This identifier can be used to get the index of every method in fastClass
    proxy.sig1 = new Signature(name1, desc);
    proxy.sig2 = new Signature(name2, desc);
    proxy.createInfo = new CreateInfo(c1, c2);
    return proxy;
}

private void init(a)
{
    /* * Using a volatile invariant allows us to initialize the FastClass and * method index pairs atomically. * * Double-checked locking is safe with volatile in Java 5. Before 1.5 this * code could allow fastClassInfo to be instantiated more than once, which * appears to be benign. */
    if (fastClassInfo == null)
    {
        synchronized (initLock)
        {
            if (fastClassInfo == null)
            {
                CreateInfo ci = createInfo;
                FastClassInfo fci = new FastClassInfo();
                // The helper generates a FastClass instance for the corresponding Class
                fci.f1 = helper(ci, ci.c1);
                fci.f2 = helper(ci, ci.c2);
                Sig1 is the original method name. Sig2 is the name of the special method generated to call the superclass method
                fci.i1 = fci.f1.getIndex(sig1);
                fci.i2 = fci.f2.getIndex(sig2);
                // Store it in local variables
                fastClassInfo = fci;
                createInfo = null; }}}}public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
    	// Call init, the FastClass index of the method currently represented by MethodProxy
        init();
        FastClassInfo fci = fastClassInfo;
        // f2 is a FastClass and fci.i2 is the index calculated in init
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException e) {
        throwe.getTargetException(); }}public Object invoke(Object obj, Object[] args) throws Throwable {
    try {
        init();
        FastClassInfo fci = fastClassInfo;
        return fci.f1.invoke(fci.i1, obj, args);
    } catch (InvocationTargetException e) {
        throw e.getTargetException();
    } catch (IllegalArgumentException e) {
        if (fastClassInfo.i1 < 0)
            throw new IllegalArgumentException("Protected method: " + sig1);
        throwe; }}private static class FastClassInfo
{
	// FastClass of the proxied class
    FastClass f1;
    // FastClass for the proxy class
    FastClass f2;
    int i1;
    int i2;
}
Copy the code

You can see that the target method of the proxied class and the target method of the proxied class (associated class object and method name) are cached in the constructor

InvokeSuper () is invoked based on the method index of the proxied class (defined here because each method has a ProxyMethod), and FastClass invoke() is used to invoke the proxied class

3. The corresponding method of the proxy class is called when the invoke() method is called

It shows that a ProxyMethod object stores the mapping relationship between the ProxyMethod and the ProxyMethod, and uses this relationship to achieve the characteristics of FastClass


FastClass mechanism

The intercepting object of JDK dynamic proxy calls the intercepted instance through reflection mechanism, which is relatively inefficient. Therefore, Cglib uses FastClass mechanism to call the intercepted method

The FastClass mechanism is to create an index for a class method, and use the index to call the corresponding method directly. Here is an example:

public class User1 {
    public void select(a) {
        System.out.println("select user1");
    }
    public void update(a) {
        System.out.println("update user1"); }}public class User2 {

    public Object invoke(int index,Object o,Object[] o1) {
        User1 user1 = (User1) o;
        switch (index) {
            case 1:
                user1.select();
                return null;
            case 2:
                user1.update();
                return null;
        }
        return null;
    }

    public int getIndex(String signature) {
        switch (signature.hashCode()) {
            case 3078479:
                return 1;
            case 3108270:
                return 2;
        }
        return -1; }}public class Test1 {
    public static void main(String[] args) {
        User1 user1 = new User1();
        User2 user2 = new User2();
        int index = user2.getIndex("update()V");
        user2.invoke(index,user1,null); }}Copy the code

In the above example, User2 is the FastClass of User1. In User2 there are two methods, getIndex() and invoke(). Each method in User1 is indexed in getIndex(). And returns the corresponding index based on the input parameter (method name + method descriptor)

The getIndex() method has already returned the specified index based on the method descriptor, passed the index to the invoke() method, passed the concrete object O and the argument OL to the method, and finally called the method directly from the object (no reflection here).

Conclusion:

GetIndex () is essentially an index table with a method descriptor that can be used to retrieve the index

Invoke (), on the other hand, can call different methods based on different indexes, thus avoiding reflection calls and improving efficiency


Let’s take a look at the two FastClass bytecode files generated:

The invoke() method is basically the same as we analyzed earlier:

1.4.3 Enhancer source analysis

We already know that one of the core classes in Cglib is Enhancer, which can be used to create proxy objects

So let’s start with an overview of the basic framework of this class:

Here you can see that the ClassGenerator is the core interface of Enhancer, where the core method generateClass() is used to generate the target class

Then attach a chain of Enhancer abstract calls:


The following uses the previous case as a template for debug analysis

CglibTest = CglibTest

public class CglibTest {
    public static void main(String[] args) {
        // Generate dynamic proxy classes in the specified directory
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E:\\mygit\\java-comprehensive\\design-patterns");
        // Create a Enhance object, similar to a proxy with JDK dynamic proxies
        Enhancer enhancer = new Enhancer();
        // Set the bytecode file of the target class
        enhancer.setSuperclass(UserDao.class);
        // Set the callback function
        enhancer.setCallback(new CustomerLogInterceptor());
        // Create a proxy object
        UserDao user = (UserDao) enhancer.create();
        // method calluser.select(); }}Copy the code

Here we create an Enhancer object and assign the parent class and interceptor to it. Take a look at the Enhancer structure before executing the create() method:

It currently stores the parent class objects, interceptor arrays, proxy generation policies, and so on…


This brings us to the key Enhancer#create method

/**
 * Generate a new class if necessary and uses the specified
 * callbacks (if any) to create a new object instance.
 * Uses the no-arg constructor of the superclass.
 * @return a new instance
 */
public Object create(a) {
   classOnly = false;
   argumentTypes = null;
   return createHelper();
}
Copy the code

As you can see from the comments, a new class object is created (if needed), and a proxy object is created if an interceptor is called if it exists

The two properties are assigned first, and the createHelper() method is called


Enhancer#createHelper

private Object createHelper(a) {
    // Verify the predeclared callback method, requiring that multiple callbacks must have a CallbackFilter as the scheduler.
    preValidate();
    // Build a key that uniquely locates such enhancement operationsObject 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 create(...) through the parent class. methods
    Object result = super.create(key);
    return result;
}
Copy the code

1. Call the preValidate() method for a pre-check, primarily to determine the current interceptor

The filter here is actually an instance of the CallbackFilter interface

2. Generate a unique key from the internal properties of the current Enhancer object, such as superClass, filter, callBackType, etc

The create() method of the parent class generates the proxy object by calling the create() method and passing in a unique key


AbstractClassGenerator#create

protected Object create(Object key) {
    try {
        // Get the ClassLoader used to load the generated class
        ClassLoader loader = getClassLoader();
        // Load the relevant data that the ClassLoader used to load from the cache
        Map<ClassLoader, ClassLoaderData> cache = CACHE;
        ClassLoaderData data = cache.get(loader);
        // If it is not found in the cache, a data instance of the ClassLoader is initialized
        if (data == null) {
            / / synchronize
            synchronized (AbstractClassGenerator.class) {
                // Reconfirm after entering the synchronized block to avoid repeated initialization builds
                cache = CACHE;
                data = cache.get(loader);
                if (data == null) {
                    // Build a new cache, copy the contents of the original cache set
                    Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
                    // Initializes ClassLoaderData, the actual construction operation
                    data = new ClassLoaderData(loader);
                    // Add to the cachenewCache.put(loader, data); CACHE = newCache; }}}this.key = key;
        Object obj = data.get(this, getUseCache());
        if (obj instanceof Class) {
            // The first instantiation is Class instantiated using reflection
            return firstInstance((Class) obj);
        }
        // Non-initial instantiation is the previously maintained content from ClassLoaderData
        return nextInstance(obj);
    } catch (RuntimeException e) {
        throw e;
    } catch (Error e) {
        throw e;
    } catch (Exception e) {
        throw newCodeGenerationException(e); }}Copy the code

1. First obtain the class loader, and then create a WeakHashMap object cache, where key is the class loader and value is the ClassLoaderData object, which directly refers to the cache static variable in the class

The ClassLoaderData class contains generated class name attributes, generated class attributes, and weak references to the classloader

protected static class ClassLoaderData {

private final Set<String> reservedClassNames = new HashSet<String>();

/ * * * {@link AbstractClassGenerator} here holds "cache key" (e.g. {@link org.springframework.cglib.proxy.Enhancer}
    * configuration), and the value is the generated class plus some additional values
    * (see {@link #unwrapCachedValue(Object)}.
    * <p>The generated classes can be reused as long as their classloader is reachable.</p>
    * <p>Note: the only way to access a class is to find it through generatedClasses cache, thus
    * the key should not expire as long as the class itself is alive (its classloader is alive).</p>
    */
   private final LoadingCache<AbstractClassGenerator, Object, Object> generatedClasses;

   /**
    * Note: ClassLoaderData object is stored as a value of {@codeWeakHashMap<ClassLoader, ... >} thus * this classLoader reference should be weak otherwise it would make classLoader strongly reachable * and alive forever. * Reference queue is not required since the cleanup is handled by {@link WeakHashMap}.
    */
   private final WeakReference<ClassLoader> classLoader;

   private final Predicate uniqueNamePredicate = new Predicate() {
      public boolean evaluate(Object name) {
         returnreservedClassNames.contains(name); }};Copy the code

2. Then get the ClassLoaderData object of the current class loader from the WeakHashMap object cache, which is the type of object we have seen above

3. Check whether the current loader data object exists. If not, create a loader data object through the current class loader and cache it

4. Assign the key attribute of the class, which is the parameter we passed in (the only key generated previously).

5. Then call data.get(this, getUseCache()) to generate the proxy class object, which will be analyzed in 1.4.3.1

6. In the following, we will analyze whether the current OBJ is either a proxy object or an object in the cache. If the data in the cache actually returns EnhancerFactoryData object

NextInstance (obj); firstInstance((Class) obj); firstInstance((Class) obj)

1.4.3.1 AbstractClassGenerator# get
public Object get(AbstractClassGenerator gen, boolean useCache) {
    // Flag to build the new generated class directly without caching
    if(! useCache) {return gen.generate(ClassLoaderData.this);
    }
    // Use buffering
    else {
      Object cachedValue = generatedClasses.get(gen);
      returngen.unwrapCachedValue(cachedValue); }}Copy the code

Generate (classloaderData. this); if not, generate the proxy object. Gen (classloaderData. this) is the Enhancer object

Generate () is actually the only entry that triggers a ClassLoader to dynamically load a generated new class object, as analyzed in 1.4.3.2

2. If caching is used, generatedclasses.get (gen) is called to try to get the cache value

3. Then call Gen. unwrapCachedValue(cachedValue) and return the result as the value

Determine whether the key is EnhancerKey, and if so, get EnhancerFactoryData data from inside the weak reference object cache passed in

1.4.3.2 AbstractClassGenerator# generate
protected Class generate(ClassLoaderData data) {
    Class gen;
    Object save = CURRENT.get();
    CURRENT.set(this);
    try {
        // Get the ClassLoader used to load the generated class
        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.");
        }
        // Build a valid generated class name (non-duplicate)
        synchronized (classLoader) {
          String name = generateClassName(data.getUniqueNamePredicate());
          data.reserveName(name);
          this.setClassName(name);
        }
        if (attemptLoad) {
            try {
                // Try loading directly from the ClassLoader
                gen = classLoader.loadClass(getClassName());
                return gen;
            } catch (ClassNotFoundException e) {
                // ignore}}// The generated class building method under the policy
        byte[] b = strategy.generate(this);
        // Get the className of the generated class by parsing the bytecode form
        String className = ClassNameReader.getClassName(new ClassReader(b));
        ProtectionDomain protectionDomain = getProtectionDomain();
        synchronized (classLoader) { // just in case
            // Load the Class Class in reflection form
            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

1. First get the classloader for the current class, then generateClassName() to generate the class name

2. Then try to get the classLoader to load the class directly with classloader.loadClass (getClassName())

3. Strategy.generate (this) generates new bytecode in the form of a specific strategy implementation, which is analyzed in 1.4.3.3

ReflectUtils. DefineClass (className,b,classLoader) will use reflection to cause the classLoader to load the new generated class, where B is the bytecode file

1.4.3.3 DefaultGeneratorStrategy# generate

We know that the argument passed here is actually an Enhancer object, because Enhancer inherits AbstractClassGenerator, thereby implementing ClassGenerator

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

This is followed by a call to the generateClass(CW) method of the ClassGenerator interface implementation class


Enhancer#generateClass

This is where the bytecode file is generated and saved to the ClassVisitor

public void generateClass(ClassVisitor v) throws Exception {
    // Determine the parent of the generated class
    Class sc = (superclass == null)? Object.class : superclass;// The parent identifier cannot be final
    if (TypeUtils.isFinal(sc.getModifiers()))
        throw new IllegalArgumentException("Cannot subclass final class " + sc.getName());
    // Get the constructor directly declared by the parent class
    List constructors = new ArrayList(Arrays.asList(sc.getDeclaredConstructors()));
    filterConstructors(sc, constructors);

    // 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();
    // Extract various information from the parent class
    getMethods(sc, interfaces, actualMethods, interfaceMethods, forcePublic);

    List methods = CollectionUtils.transform(actualMethods, new Transformer() {
        public Object transform(Object value) {
            Method method = (Method)value;
            int modifiers = Constants.ACC_FINAL
                | (method.getModifiers()
                   & ~Constants.ACC_ABSTRACT
                   & ~Constants.ACC_NATIVE
                   & ~Constants.ACC_SYNCHRONIZED);
            if (forcePublic.contains(MethodWrapper.create(method))) {
                modifiers = (modifiers & ~Constants.ACC_PROTECTED) | Constants.ACC_PUBLIC;
            }
            returnReflectUtils.getMethodInfo(method, modifiers); }}); ClassEmitter e =new ClassEmitter(v);
    if (currentData == null) {
    e.begin_class(Constants.V1_2,
                  Constants.ACC_PUBLIC,
                  getClassName(),
                  Type.getType(sc),
                  (useFactory ?
                   TypeUtils.add(TypeUtils.getTypes(interfaces), FACTORY) :
                   TypeUtils.getTypes(interfaces)),
                  Constants.SOURCE_FILE);
    } else {
        e.begin_class(Constants.V1_2,
                Constants.ACC_PUBLIC,
                getClassName(),
                null.new Type[]{FACTORY},
                Constants.SOURCE_FILE);
    }
    List constructorInfo = CollectionUtils.transform(constructors, MethodInfoTransformer.getInstance());

    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);
    if(! interceptDuringConstruction) { e.declare_field(Constants.ACC_PRIVATE, CONSTRUCTED_FIELD, Type.BOOLEAN_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);
    if(serialVersionUID ! =null) {
        e.declare_field(Constants.PRIVATE_FINAL_STATIC, Constants.SUID_FIELD_NAME, Type.LONG_TYPE, serialVersionUID);
    }

    for (int i = 0; i < callbackTypes.length; i++) {
        e.declare_field(Constants.ACC_PRIVATE, getCallbackField(i), callbackTypes[i], 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);

    if (currentData == null) {
        emitMethods(e, methods, actualMethods);
        emitConstructors(e, constructorInfo);
    } else {
        emitDefaultConstructor(e);
    }
    emitSetThreadCallbacks(e);
    emitSetStaticCallbacks(e);
    emitBindCallbacks(e);

    if(useFactory || currentData ! =null) {
        int[] keys = getCallbackKeys();
        emitNewInstanceCallbacks(e);
        emitNewInstanceCallback(e);
        emitNewInstanceMultiarg(e, constructorInfo);
        emitGetCallback(e, keys);
        emitSetCallback(e, keys);
        emitGetCallbacks(e);
        emitSetCallbacks(e);
    }

    e.end_class();
}
Copy the code

My personal official account is under preliminary construction now. If you like, you can follow my official account. Thank you!