Java foundation - Dynamic Proxy

Keywords: JDK Dynamic Proxy cglib

preface

In the source code implementation of Mybatis, the design idea of dynamic agent is used. In order to understand the dynamic agent in Mybatis, this article will study the JDK dynamic agent combined with examples and source code, and finally summarize the differences between JDK dynamic agent and CGLIB dynamic agent, so as to help better understand the dynamic agent and lay a foundation for the learning of Mybatis source code.

text

1, Agent mode

Before learning about dynamic agents, review the agent pattern in design patterns. Proxy mode is defined as providing a proxy object to the proxy object to control access to the proxy object, that is, when the access object is not suitable or cannot directly reference the proxy object, the proxy object acts as an intermediary between the access object and the proxy object. In the proxy mode, there are three roles: AbstractSubject, RealSubject and proxy. The meanings of the three roles are shown in the table below.

rolemeaning
Abstract subjectDeclare real topics and business methods to be implemented by agents through interfaces or abstract classes.
Real subjectIt implements the business method in the abstract topic. It is the real object represented by the agent and the object to be referenced finally.
ProxyIt implements the abstract theme and provides the same method as the real theme. It contains references to the real theme, which can access, control or expand the functions of the real theme.

The relationship between the three roles of the agent pattern is represented by a class diagram as follows.

2, Static proxy

According to the creation time of the bytecode file of the agent class in the agent mode, the agent can be divided into static agent and dynamic agent: the bytecode file of the agent class already exists before the program runs, while the dynamic agent generates the bytecode file for the agent class by the JVM through the reflection mechanism during the program runs. This section takes an example to learn about static agents.

Define an abstract topic, as shown below.

public interface TestServiceA {

    void executeTestA();
    void submitTestA();

}

public interface TestServiceB {

    void executeTestB();
    void submitTestB();

}

The above defines two interfaces as abstract topics, and the following defines a real topic to implement abstract topics, as shown below.

public class RealObject implements TestServiceA, TestServiceB {

    @Override
    public void executeTestA() {
        System.out.println("Test A execute.");
    }

    @Override
    public void submitTestA() {
        System.out.println("Test A submit.");
    }

    @Override
    public void executeTestB() {
        System.out.println("Test B execute.");
    }

    @Override
    public void submitTestB() {
        System.out.println("Test B submit.");
    }

}

Define another proxy class, as shown below.

public class ProxyObject implements TestServiceA, TestServiceB {

    private RealObject realObject;

    public ProxyObject(RealObject realObject) {
        this.realObject = realObject;
    }

    @Override
    public void executeTestA() {
        before();
        realObject.executeTestA();
        after();
    }

    @Override
    public void submitTestA() {
        before();
        realObject.submitTestA();
        after();
    }

    @Override
    public void executeTestB() {
        before();
        realObject.executeTestB();
        after();
    }

    @Override
    public void submitTestB() {
        before();
        realObject.submitTestB();
        after();
    }

    private void before() {
        System.out.println("Begin to do.");
    }

    private void after() {
        System.out.println("Finish to do.");
    }

}

It can be seen that both real topic RealObject and proxy ProxyObject implement abstract topics. At the same time, proxy ProxyObject also holds references to real topic RealObject. Therefore, you need ProxyObject to access RealObject. At the same time, ProxyObject can execute some additional logic to expand the functions of RealObject when executing RealObject methods. Write a client program, as shown below.

public class ClientOne {

    public static void main(String[] args) {
        RealObject realObject = new RealObject();
        ProxyObject proxyObject = new ProxyObject(realObject);
        proxyObject.executeTestA();
        proxyObject.submitTestA();
        proxyObject.executeTestB();
        proxyObject.submitTestB();
    }

}

The operation results are as follows.

3, JDK dynamic agent

Think about the shortcomings of the static agent in the second section in practical use? It can be summarized as follows.

  • If a proxy class is allowed to represent multiple proxy classes, the proxy class will become too large;
  • If each proxy class corresponds to a proxy class, there will be too many proxy classes;
  • Because both the proxy class and the proxy class need to implement the same interface, when the methods defined by the interface are increased or reduced, the proxy class and the proxy class need to be modified together, which is not easy to maintain the code.

The problems of the above static agent can be solved by the dynamic agent, that is, the generation of the agent class is determined only during the running of the program. The following describes the use of dynamic proxy according to an example based on JDK dynamic proxy, and then analyzes the implementation mechanism of JDK dynamic proxy and why proxy classes can be generated dynamically based on the source code.

JDK dynamic Proxy is mainly based on two classes: java.lang.reflect.Proxy and java.lang.reflect.InvocationHandler. All Proxy classes generated based on JDK dynamic Proxy will inherit from the Proxy. At the same time, the Proxy class will hold the reference of InvocationHandler, and the InvocationHandler will hold the reference of the Proxy class, Therefore, InvocationHandler can be understood as the intermediary between the Proxy class and the proxied class. First, create a class to implement the InvocationHandler interface, as shown below.

public class TestInvocationHandler implements InvocationHandler {

    private Object realObject;

    public TestInvocationHandler(Object realObject) {
        this.realObject = realObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object invokeResult = method.invoke(realObject, args);
        after();
        return invokeResult;
    }

    private void before() {
        System.out.println("Begin to do.");
    }

    private void after() {
        System.out.println("Finish to do.");
    }

}

As shown above, TestInvocationHandler implements the InvocationHandler interface. At the same time, there is a member variable named realObject in TestInvocationHandler, which is the proxy class. When the proxy class executes the proxy method, it will call the method of the proxy class through TestInvocationHandler, At the same time, TestInvocationHandler can also define some methods to realize function expansion. In the above example, before() and after() methods are defined to do some things before and after the execution of the proxy class method.

After creating the TestInvocationHandler, you can start to create the dynamic proxy class. The proxy class still follows the RealObject in Section 2. The logic of creating the dynamic proxy class is as follows.

public class ClientTwo {

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

        //Create proxied class
        RealObject realObject = new RealObject();
        //Gets the class loader of the proxied class
        ClassLoader classLoader = realObject.getClass()
                .getClassLoader();
        //Gets the Class object of the interface implemented by the proxy Class
        Class<?>[] interfaces = realObject.getClass()
                .getInterfaces();
        //Create InvocationHandler with the proxied class as an input parameter
        InvocationHandler invocationHandler
                = new TestInvocationHandler(realObject);
        //Create a dynamic Proxy object by calling the Proxy's newProxyInstance() method
        Object proxyInstance = Proxy.newProxyInstance(
                classLoader, interfaces, invocationHandler);

        ((TestServiceA) proxyInstance).executeTestA();
        ((TestServiceA) proxyInstance).submitTestA();
        ((TestServiceB) proxyInstance).executeTestB();
        ((TestServiceB) proxyInstance).submitTestB();
    }

}

Run the above program, and the execution results are as follows.

View the bytecode file of the generated proxy class in the project directory / com/sun/proxy, and decompile as shown below.

public final class $Proxy0 extends Proxy implements TestServiceA, TestServiceB {

    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m6;
    private static Method m5;
    private static Method m0;
    private static Method m4;

    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 new UndeclaredThrowableException(var4);
        }
    }

    public final void executeTestA() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void executeTestB() throws  {
        try {
            super.h.invoke(this, m6, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void submitTestB() throws  {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void submitTestA() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceA").getMethod("executeTestA");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m6 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceB").getMethod("executeTestB");
            m5 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceB").getMethod("submitTestB");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m4 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceA").getMethod("submitTestA");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

}

You can see that the generated Proxy class inherits from the Proxy and also implements the interface implemented by the Proxy class. When the Proxy class executes the Proxy method, it will call the real method of the Proxy class through its InvocationHandler inherited from the Proxy. So far, an example of JDK dynamic Proxy is introduced here. Now take a look at what the Proxy.newProxyInstance() method does to understand why Proxy classes can be generated dynamically. The method source code is as follows.

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException {
    
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    //Generate Class object of proxy Class
    Class<?> cl = getProxyClass0(loader, intfs);

    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        //Gets the constructor of the proxy class
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        //Generate proxy object
        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 new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

The getProxyClass0() method will generate the Class object of the Proxy Class. The generated Class object of the Proxy Class will be cached in the Proxy Class variable proxyClassCache. Therefore, the getProxyClass0() method will first obtain the Proxy Class object in the proxyClassCache. If it cannot be obtained, the Proxy Class object will be generated through ProxyClassFactory.

ProxyClassFactory is a static internal class of Proxy, which mainly completes two things.

  • Generate bytecode file of proxy class;
  • Call the native method defineClass0() to parse the bytecode file of the proxy Class and generate the Class object of the proxy Class.

When generating the bytecode file of the proxy class in ProxyClassFactory, the generateProxyClass() method of the ProxyGenerator is called. Before generating the bytecode file, the hashCode(), equals() and toString() methods of the Object and the methods defined by the interface implemented by the proxy class will be added to the methods of the proxy class.

So far, you can summarize how JDK dynamic agent dynamically generates agent classes as follows.

4, CGLIB dynamic proxy

In JDK dynamic proxy, the proxy class is required to implement the interface, which limits the use of JDK dynamic proxy. When the proxy class does not implement the interface, CGLIB dynamic proxy can be used to dynamically generate the proxy class. The proxy class generated by CGLIB is a subclass of the proxy class. This section will explain the use of CGLIB in combination with examples.

First create a proxied class, as shown below.

public class RealService {

    public void execute(String flag) {
        System.out.println("Test " + flag + " execute.");
    }

    public void submit(String flag) {
        System.out.println("Test " + flag + " submit.");
    }

}

Then create a method interceptor. The method interceptor needs to inherit from the MethodInterceptor to intercept when the proxy object executes the method, as shown below.

public class TestInterceptor implements MethodInterceptor {

    /**
     * @param o Proxy object
     * @param method Method of proxy object
     * @param objects The method parameter type of the proxied object
     * @param methodProxy Method of proxy object
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;
    }

    private void before() {
        System.out.println("Begin to do.");
    }

    private void after() {
        System.out.println("Finish to do.");
    }

}

The above method interceptor will intercept each proxy object's method execution, and then execute the before() method, the proxy object's method and the after() method in turn to enhance the proxy object's method. At the same time, the first parameter of the intercept() method is the proxy object, so you need to use invokeSuper() to execute the proxy object's method.

Finally, create a client program to test the effect, as shown below.

public class ClientThree {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealService.class);
        enhancer.setCallback(new TestInterceptor());
        Object proxyObject = enhancer.create();

        ((RealService) proxyObject).execute("cglib");
        ((RealService) proxyObject).submit("cglib");
    }

}

The operation results are as follows.

CGLIB dynamic agent can also save the bytecode file of agent class. Only the following settings are required.

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, Save path);

Now, after decompiling the bytecode file through IDEA, you can view the generated proxy class. The following section is intercepted to explain the calling and enhancement of proxy methods, as shown below.

public class RealService$$EnhancerByCGLIB$$64276695 extends RealService implements Factory {
    
    ......

    static void CGLIB$STATICHOOK1() {
        ......
        CGLIB$execute$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "execute", "CGLIB$execute$0");
        ......
    }

    ......

    public final void execute(String var1) {
        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$execute$0$Method, new Object[]{var1}, CGLIB$execute$0$Proxy);
        } else {
            super.execute(var1);
        }
    }
    
    ......

}

Looking at the decompiled proxy class, we can know that the proxy class generated by CGLIB dynamic proxy is a subclass of the proxy class, and when the contemporary management class calls methods, it will call the methods and enhanced methods of the proxy class through MethodInterceptor.

So far, the example of CGLIB dynamic agent has been introduced. Compared with JDK dynamic agent, CGLIB dynamic agent dynamically generates the bytecode file of agent class through bytecode processing framework ASM and loads it into the JVM. The following is a simple comparison between JDK dynamic agent and CGLIB dynamic agent.

JDK dynamic agent

  • In JDK dynamic proxy, the proxy class calls the method of the proxy class, which depends on the InvocationHandler interface;
  • JDK dynamic proxy requires the proxy class to implement one or more interfaces;
  • JDK dynamic proxy is a bytecode file that dynamically generates proxy classes based on reflection.

CGLIB dynamic proxy

  • In CGLIB dynamic proxy, the proxy class calls the method of the proxy class, which depends on the MethodInterceptor interface;
  • CGLIB dynamic proxy requires that the proxy class cannot be final, but does not require the proxy class to implement the interface;
  • CGLIB dynamic proxy cannot proxy the final method in the proxy class;
  • CGLIB dynamic proxy is a bytecode file that dynamically generates proxy classes based on ASM framework.

summary

This paper discusses the agent design pattern, static agent, JDK dynamic agent and CGLIB dynamic agent. The static proxy is the simplest to implement. After the program is compiled, the bytecode file of the proxy class has been generated and can be directly loaded into memory by the JVM. It has high efficiency and saves the time of generating bytecode file in the dynamic proxy. However, the disadvantage is that in the static proxy, usually one proxy class only proxies one proxy class. If there are too many proxy classes, It will lead to too many proxy classes. Dynamic agent can solve the problem of too many agent classes. JDK dynamic agent can dynamically generate the bytecode file of agent class based on reflection during program operation, but the proxy class is required to implement the interface, which limits the use scenario of JDK dynamic agent, while CGLIB dynamic agent does not require the proxy class to implement the interface, The bottom layer is to dynamically generate the bytecode file of the proxy class based on the ASM framework. The proxy class created by CGLIB is a subclass of the proxy class, so CGLIB dynamic proxy requires that the proxy class cannot be final.

Posted by zone16 on Mon, 08 Nov 2021 04:04:53 -0800