Interview build rocket series, planted in cglib and jdk dynamic agent

Posted by dgwade on Fri, 31 Dec 2021 18:46:25 +0100

Hello, I'm the technical interviewer of XX Baba company. Are you Zhang Xiaoshuai. The voice came from the other end of the phone

Yes, hello. Xiaoshuai was secretly pleased that Dachang finally found me.

"Let's have a telephone interview. Please introduce yourself first."

“balabalabla...” Xiaoshuai gave a general description of his previous experience

"Well, I have a lot of experience. Let's talk about technology. What's the difference between cglib and jdk dynamic agent?"

"Er (⊙ o ⊙)...", Zhang Xiaoshuai was stunned and the scene was once embarrassing.

......

The interview happened just now, because the first question was planted, and the interviewer's question later, Xiaoshuai basically lost his confidence. At this time, Xiaoshuai was in a restless mood for a long time, "is this the interview of a big factory?", Xiaoshuai muttered to himself. What he never expected was that the first question fell.

Does the little partner in front of the screen know well? Next, let's go over it with the old cat.

When talking about dynamic agents, let's start with the agent model.

proxy pattern

The proxy mode is defined by consulting professional materials: the proxy mode provides a proxy object for an object, and the proxy object controls the reference to the original object.

It mainly solves the problems caused by directly accessing objects. For example, the object to be accessed is on a remote machine. In an object-oriented system, direct access to some objects will bring a lot of trouble to users or system structure for some reasons (such as high object creation cost, or some operations need security control, or need out of process access). We can add an access layer to this object when accessing this object.

The above concepts seem vague. For example, train tickets are a target object. If we want to buy them, we don't have to go to the railway station to buy them. In fact, we can buy them at many agency points. For another example, when pig Bajie went to Gao Cuilan, the result was that the monkey king changed. It can be understood as follows: Abstract Gao Cuilan's appearance. Gao Cuilan himself and the monkey king have implemented this interface. When pig Bajie visited Gao Cuilan, he can't see that this is the monkey king, so Monkey King is Gao Cuilan's agent class.

Static proxy mode

Code demonstration:
Let's demonstrate the agency mode through the case of buying train tickets. The specific code is as follows:
Abstract interface:

/**
 * @Author: Old cat
 * @Description: ticket
 */
public interface Ticket {
    void getTicket();
}

The railway station realizes the abstract interface and has the function of buying tickets

/**
 * @Author: Old cat
 * @Description: train station
 */
public class RailwayStation implements Ticket {

    @Override
    public void getTicket() {
        System.out.println("Bought a train ticket");
    }
}

The railway station agent class implements the abstract interface and has the function of buying tickets

/**
 * @Author: Old cat
 * @Description: proxy class
 * @Date: 2021/12/22 5:35 afternoon
 */
public class RailwayAgencyProxy implements Ticket{
    private RailwayStation railwayStation;

    public RailwayAgencyProxy(RailwayStation railwayStation) {
        this.railwayStation = railwayStation;
    }

    @Override
    public void getTicket() {
        railwayStation.getTicket();
    }
}

The above is actually the static proxy mode.

advantage:

  1. The function of the target object can be extended in accordance with the opening and closing principle.
  2. The responsibilities are very clear and clear at a glance.

Disadvantages:

  1. Due to the addition of proxy objects between the client and the real topic, some types of proxy patterns may slow down the processing of requests.
  2. Implementing the proxy pattern requires additional work, and the implementation of some proxy patterns is very complex.
  3. At the code level, if the interface changes, the proxy class will also change.

Dynamic agent

With the above foundation, let's officially talk about dynamic agents.
In the above example, it is not difficult to find that each proxy class can only implement one interface service. Then, if there are multiple business types suitable for agent mode in our software engineering, we will create multiple agent classes. How can we solve this problem? In fact, our dynamic agent came into being.

Obviously, the bytecode of the dynamic proxy class is dynamically generated by the Java reflection mechanism when the program is running, and there is no need for us to write its source code manually.
Let's take a look at the JDK dynamic proxy class based on the above case

JDK dynamic agent

Take a direct look at the use of JDK dynamic agent, as shown in the following code block

public class JDKDynamicProxy implements InvocationHandler {

    //Proxied object
    private Object object;

    public JDKDynamicProxy(Object object) {
        this.object = object;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object,args);
        return result;
    }

    //Generate proxy class
    public Object createProxyObj(){
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
    }
}

Static proxy and dynamic proxy are called as follows:

public class TestProxy {
    public static void main(String[] args) {
        //Static proxy test
        RailwayStation railwayStation = new RailwayStation();
        railwayStation.getTicket();

        RailwayAgencyProxy railwayAgencyProxy = new RailwayAgencyProxy(railwayStation);
        railwayAgencyProxy.getTicket();

        //Dynamic agent testing
        Ticket ticket = new RailwayStation();
        JDKDynamicProxy jdkDynamicProxy = new JDKDynamicProxy(ticket);
        Ticket proxyBuyTicket = (Ticket) jdkDynamicProxy.createProxyObj();
        proxyBuyTicket.getTicket();
    }
}

By observing the above dynamic agent and static agent tests, the advantages of dynamic agent are obvious. If we demonstrate another proxy scenario of Zhu Bajie and Gao Cuilan, we don't need to create GaocuiLanProxy. We just need to create proxy classes through jdkddynamicproxy.

Note proxy The newproxyinstance() method accepts three parameters:

  1. Classloader: Specifies the class loader used by the current target object. The method to obtain the loader is fixed.
  2. Class<?> [] interfaces: specify the type of interface implemented by the target object, and confirm the type using generic method
  3. InvocationHandler: Specifies the dynamic processor. When executing the method of the target object, the method of the event handler will be triggered

Through the above examples and the above parameters, it is not difficult to find that JDK dynamic agent has such a feature:
JDK dynamic proxy is an interface oriented proxy mode. If you want to use JDK proxy, you must first have an interface, such as the Ticket interface in the above example

cglib dynamic proxy

Let's take another look at cglib dynamic proxy. Let's first understand what cglib is. In fact, there are few official explanations about cglib, but it is very powerful, which is also criticized by many people. CGLIB(Code Generation Library) is an open source project! It is a powerful, high-performance and high-quality code generation class library. It can extend Java classes and implement Java interfaces at run time. Cglib is a powerful high-performance code generation package. It is widely used by many AOP frameworks, For example, Spring AOP provides them with the intercept of the method (interception). The bottom layer of the cglib package is to convert bytecode and generate new classes by using a small and fast bytecode processing framework ASM. In addition to the cglib package, scripting languages such as Groovy and BeanShell also use ASM to generate Java bytecode. Of course, the direct use of ASM is not encouraged, because it requires you to be familiar with the internal structure of the JVM, including the format and instruction set of class files Familiar.

Next, let's look at the usage. Since cglib is not built in the jdk, if it is a maven project, we first need to introduce the pom dependencies related to cglib, as follows:

<dependency>    
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

Because the object of cglib proxy is a class, this is different from JDK dynamic proxy. This place is marked with red and emphasis.
In this case, if we have the same agent class, we should define it as follows,

public class Ticket {
    public void getTicket(){   
        System.out.println("Bought a train ticket");  
    }   
    
     final public void refundTicket(){  
        System.out.println("Returned a train ticket"); 
      }
 }

Obviously, the above class defines two methods, one is to buy a train ticket, and the other is to return a train ticket.

public class CglibDynamicProxy implements MethodInterceptor {    
        
    /**
     * @param o cglib Generated proxy object
     * @param method Method of proxy object
     * @param objects Parameters passed in to the method
     * @param methodProxy Proxy method
     * @return
     * @throws Throwable
     */   
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("execute pre");
            Object obj = methodProxy.invokeSuper(o,objects);
            System.out.println("execute after");
             return obj;
    }
}

Call test entry method call

public class TestCglibProxy {
    public static void main(String[] args) {
        // The proxy class file is stored on the local disk for us to decompile and view the source code
   System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/kdaddy/project/log");
        // The process of obtaining proxy objects through CGLIB dynamic proxy
        Enhancer enhancer = new Enhancer();
        // Sets the parent class of the enhancer object
        enhancer.setSuperclass(Ticket.class);
        // Sets the callback object for enhancer
        enhancer.setCallback(new CglibDynamicProxy());
        // Create proxy object
        Ticket ticket = (Ticket) enhancer.create();
        // Call the target method through the proxy object
        ticket.getTicket();
        // Try calling the final object through the proxy to call the target method
        ticket.refundTicket();
    }
}

After running, we get the following results:

execute pre
 Bought a train ticket
execute after
 A train ticket was cancelled

According to the log printing, it is easy to find that the relevant printing "cancelled train ticket" is not represented, so we can draw a conclusion that cglib dynamic agent cannot represent the method modified by final.

In the above source code, we mentioned that the proxy class is written to the relevant disk. Open the corresponding directory and we will find the following three files

The Ticket $$enhancerbycglib $$4e79a04 a class is a proxy class generated by cglib, which becomes a Ticket.
Let's take a look at the relevant source code:

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

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("kdaddy.com.cglibDynamic.Ticket$$EnhancerByCGLIB$$4e79a04a");
        Class var1;
        CGLIB$getTicket$0$Method = ReflectUtils.findMethods(new String[]{"getTicket", "()V"}, (var1 = Class.forName("kdaddy.com.cglibDynamic.Ticket")).getDeclaredMethods())[0];
        CGLIB$getTicket$0$Proxy = MethodProxy.create(var1, var0, "()V", "getTicket", "CGLIB$getTicket$0");
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
    }
}    

We can see from the source code of the proxy class that the proxy class will obtain all the methods inherited from the parent class, and there will be MethodProxy corresponding to it, except for the methods modified by final. We really don't see the previous refundTicket method in the above source code. Now look down.

Let's look at the call of one of the methods.

//Proxy method (methodProxy.invokeSuper will call)
final void CGLIB$getTicket$0() {
        super.getTicket();
    }

//The proxy method (what methodProxy.invoke calls, that is why calling methodProxy.invoke in the interceptor will loop, calling the interceptor).
    public final void getTicket() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
             //Call interceptor
            var10000.intercept(this, CGLIB$getTicket$0$Method, CGLIB$emptyArgs, CGLIB$getTicket$0$Proxy);
        } else {
            super.getTicket();
        }
    }

From the above, let's look at the whole call link of getTicket:
Call getTicket() method - > call interceptor - > methodproxy Invokesuper - > cglib $getTicket $0 - > getTicket method of the proxy.

Next, let's take a look at the core method proxy. Let's take a direct look at the core: Method proxy Invokesuper. The specific source code is as follows:

 public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }
    
//Take a closer look at FastClassInfo
private static class FastClassInfo {
        FastClass f1;//Proxied class FastClass
        FastClass f2;//Proxy class FastClass
        int i1; //Method signature of the proxy class (index)
        int i2;//Method signature of proxy class

        private FastClassInfo() {
        }
    }

The above code call process is to obtain the FastClass corresponding to the proxy class and execute the proxy method. Remember generating three class files before? Ticket$$EnhancerByCGLIB$e79a04a$$FastClassByCGLIB$$f000183.class is the FastClass of the proxy class, ticket $$fastclassbycglib $$a79cabb2 Class is the FastClass of the proxy class.

About FastClass
The reason why the efficiency of Cglib dynamic agent executing proxy methods is higher than that of JDK is that Cglib adopts the FastClass mechanism. Its principle is simply to generate a Class for the proxy Class and the proxy Class respectively. This Class will assign an index(int type) to the methods of the proxy Class or the proxy Class.
Using this index as an input parameter, FastClass can directly locate the method to be called and call it directly, which eliminates the reflection call, so the call efficiency is higher than that of JDK dynamic agent through reflection call. Let's decompile a FastClass to see:

public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch(var10000.hashCode()) {
        case -80792013:
            if (var10000.equals("getTicket()V")) {
                return 0;
            }
            break;
        case 189620111:
            if (var10000.equals("cancelTicket()V")) {
                return 1;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return 2;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return 3;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return 4;
            }
        }

        return -1;
    }
    ...Some codes are omitted here...
    public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        Ticket var10000 = (Ticket)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 0:
                var10000.getTicket();
                return null;
            case 1:
                var10000.cancelTicket();
                return null;
            case 2:
                return new Boolean(var10000.equals(var3[0]));
            case 3:
                return var10000.toString();
            case 4:
                return new Integer(var10000.hashCode());
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }
    

FastClass is not generated together with the proxy class, but is generated when the MethodProxy invoke/invokeSuper is executed for the first time and placed in the cache.

//Both MethodProxy invoke/invokeSuper call init()
private void init() {
        if(this.fastClassInfo == null) {
            Object var1 = this.initLock;
            synchronized(this.initLock) {
                if(this.fastClassInfo == null) {
                    MethodProxy.CreateInfo ci = this.createInfo;
                    MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                    fci.f1 = helper(ci, ci.c1);//If you take it out of the cache and generate a new FastClass without it, the interested partners here can take a closer look at the underlying source code. The old cat mentioned it here.
                    fci.f2 = helper(ci, ci.c2);
                    fci.i1 = fci.f1.getIndex(this.sig1);//Gets the index of the method
                    fci.i2 = fci.f2.getIndex(this.sig2);
                    this.fastClassInfo = fci;
                    this.createInfo = null;
                }
            }
        }

    }

summary

If you are like Zhang Xiaoshuai on the screen, how should you deal with it? In fact, most of the answers are above. To sum up
(1) JDK dynamic proxy implements the interface of the proxy object, and Cglib inherits the proxy object.
(2) Both JDK and cglib generate bytecode during runtime. JDK directly writes Class bytecode. Cglib uses ASM framework to write Class bytecode. The implementation of cglib agent is more complex, and the generation agent is inefficient compared with JDK.
(3) JDK calls proxy methods through reflection mechanism. Cglib calls methods directly through FastClass mechanism. Cglib has higher execution efficiency.

 

Topics: Java Maven Redis Spring Back-end