Simple Implementation and Principle Analysis of Dynamic Proxy

Foreword

This article will combine examples and source code to learn JDK dynamic proxy, and finally summarize the difference between JDK dynamic proxy and CGLIB dynamic proxy , to help better understand dynamic proxies.

Text

1. Proxy mode

Before learning dynamic proxy, let’s review the proxy mode in the design mode.

The 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 refer to the proxy object, the proxy object acts as an intermediary between the access object and the proxy object.

In proxy mode, there are three roles, namely abstract subject (AbstractSubject), real subject (RealSubject) and proxy (Proxy), three The role meanings are shown in the table below.

Abstract Subject (AbstractSubject) Declare real subjects and business methods that agents need to implement through interfaces or abstract classes
Real subjects (RealSubject) Implements the business method in the abstract theme, is the real object represented by the proxy, and is the final object to be referenced
Proxy (Proxy ) Implements the abstract theme, provides the same method as the real theme, and contains references to the real theme inside, which can access, control or extend the functions of the real theme

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

Design mode-proxy mode UML diagram

2. Static proxy

According to the creation timing of the bytecode file of the proxy class in the proxy mode, the proxy can be divided into static proxy and dynamic proxy: before the program runs, the proxy class The bytecode file already exists, and the dynamic proxy is to generate a bytecode file for the proxy class by JVM through the reflection mechanism during the running of the program. This section uses an example to study static proxy.

Define abstract topics 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 a real topic is defined below 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.");
    }

}

Then define a 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 the real theme RealObject and the proxy ProxyObject implement the abstract theme, and the proxy ProxyObject also holds the real theme RealObject< /strong>, so you need to access RealObject through ProxyObject, and ProxyObject is executing the method of RealObject , you can also perform some additional logic to extend the functionality of the RealObject. 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 running results are shown below.

Static proxy execution result map

3. JDK dynamic proxy

Think about it, what are the shortcomings of the static proxy in the second section in actual use? Here are summarized as follows.

  • If a proxy class is used to represent multiple proxy classes, it will cause the proxy class to become too large;
  • If each proxy class corresponds to a proxy class, it will cause too many proxy classes;
  • Since both the proxy class and the proxy class need to implement the same interface, when the method defined by the interface increases or decreases, the proxy class and the proxy class need to be modified together, which is not easy to maintain the code.

The above-mentioned problems of static proxy can be solved by dynamic proxy, that is, the generation of proxy class is decided during the running of the program. The following is an example based on JDK dynamic proxy to illustrate the use of dynamic proxy, and then analyze the implementation mechanism of JDK dynamic proxy and why the proxy can be dynamically generated based on the source code kind.

JDK dynamic proxy is mainly based on two classes: java.lang.reflect.Proxy and java.lang.reflect.InvocationHandler, all based on The proxy class generated by strong>JDK dynamic proxy will inherit from Proxy, and the proxy class will hold the reference of InvocationHandler, and InvocationHandler strong> will hold a reference to the proxy class, so InvocationHandler can be understood as an intermediary between the proxy class and the proxy class.

First create a class that implements 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, and there is a member variable named realObject in TestInvocationHandler, This variable 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 by itself To achieve function extension, in the above example, two methods before() and after() are defined, which are respectively used before and after the execution of the proxy class method do something.

After creating TestInvocationHandler, you can start to create dynamic proxy classes. The proxy class still uses RealObject in the second section. The logic of creating dynamic proxy classes is as follows Show.

public class ClientTwo {<!-- -->

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

        // create proxy class
        RealObject realObject = new RealObject();
        // Get the class loader of the proxy class
        ClassLoader classLoader = realObject. getClass()
                .getClassLoader();
        // Get the Class object of the interface implemented by the proxy class
        Class<?>[] interfaces = realObject. getClass()
                .getInterfaces();
        // Create an InvocationHandler with the proxy class as an input parameter
        InvocationHandler invocationHandler
                = new TestInvocationHandler(realObject);
        // Create a dynamic proxy object by calling 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, the execution result is as follows.

JDK dynamic proxy execution result

View the bytecode file of the generated proxy class under project directory/com/sun/proxy, and decompile it 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());
        }
    }

}

It can be seen that the generated proxy class inherits from Proxy, and also implements the interface implemented by the proxy class. When the proxy class executes the proxy method, it will inherit from Proxy strong>’s InvocationHandler to call the real method of the proxy class. So far, an example of JDK dynamic proxy is introduced here. Now look at what the Proxy.newProxyInstance() method does to understand why proxy classes can be dynamically generated. The source code of the method 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 the Class object of the proxy class
    Class<?> cl = getProxyClass0(loader, intfs);

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

        // Get 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, and the Class object of the generated proxy class will be cached in Proxy strong> in the class variable proxyClassCache, so the getProxyClass0() method will first obtain the proxy class Class in proxyClassCache object, if it cannot be obtained, the proxy class Class object will be generated through ProxyClassFactory.

ProxyClassFactory is a static inner class of Proxy, which mainly accomplishes two things.

  • Generate the bytecode file of the 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 ProxyGenerator is called, and the bytecode file is generated Before, the hashCode(), equals() and toString() methods of Object and the proxy class The methods defined by the implemented interface are added to the methods of the proxy class.

So far, how JDK dynamic proxy dynamically generates proxy classes can be summarized as follows.

JDK dynamic proxy generation schematic diagram

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, you want to dynamically To generate a proxy class, you can use CGLIB dynamic proxy. The proxy class generated by using CGLIB is a subclass of the proxy class. This section will combine examples to analyze CGLIB The use of strong> is explained.

First create a proxy 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, which needs to be inherited from MethodInterceptor to intercept when the proxy object executes the method, as shown below.

public class TestInterceptor implements MethodInterceptor {<!-- -->

    /**
     * @param o proxy object
     * @param method The method of the proxy object
     * @param objects The method parameter type of the proxied object
     * @param methodProxy the proxy of the method of the proxied 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 method of the proxy object when it is executed, and then execute the before() method, the method of the proxy object and the after() method in turn, In order to achieve the enhanced effect on the method of the proxy object, at the same time, the first parameter of the intercept() method is the proxy object, so if you want to execute the method of the proxy object, you need to use invokeSuper() .

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 running results are shown below.

CGLIB dynamic proxy execution result

CGLIB The dynamic proxy can also save the bytecode file of the proxy class, just do the following settings.

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 intercepts a part to illustrate the invocation and enhancement of the proxy method, 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 proxy class calls a method, it will pass MethodInterceptor To call the methods and enhanced methods of the proxy class.

So far, the example of CGLIB dynamic proxy has been introduced. Compared with JDK dynamic proxy, CGLIB dynamic proxy is through the bytecode processing frameworkASM to dynamically generate the bytecode file of the proxy class and load it into the JVM.

The following is a simple comparison of JDK dynamic proxy and CGLIB dynamic proxy.

JDKdynamic proxy

  • In JDK dynamic proxy, the proxy class calls the method of the proxy class to rely on the InvocationHandler interface;
  • JDK Dynamic proxy requires that the proxy class needs to implement one or more interfaces;
  • JDK dynamic proxy is based on reflection to dynamically generate bytecode files of proxy classes.

CGLIBdynamic proxy

  • In CGLIB dynamic proxy, the proxy class calls the method of the proxy class to rely 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 based on ASM framework to dynamically generate bytecode files of proxy classes.

A process about the execution method of the CGLIB proxy object will be explained in an article later.

Summary

This article is about Proxy Design Pattern, Static Proxy, JDKDynamic Proxy, CGLIB Dynamic proxies are discussed.

Static proxy is the easiest to implement. After the program is compiled, the bytecode file of the proxy class has been generated and can be directly loaded into the memory by the JVM, which is highly efficient and saves The time to generate bytecode files in dynamic proxies is gone, but the disadvantage is that in static proxies, a proxy class usually only proxies one proxied class. If there are too many proxied classes, there will be too many proxy classes.

JDK dynamic proxy can dynamically generate the bytecode file of the proxy class based on reflection during the running of the program, but requires the proxy class to implement the interface, which limits the use of JDK dynamic proxy scenes to be used.

CGLIB The dynamic proxy does not require the proxy class to implement the interface, and its bottom layer is based on the ASM framework to dynamically generate the bytecode file of the proxy class, CGLIB The created proxy class is a subclass of the proxy class, so CGLIB dynamic proxy requires that the proxy class cannot be final.

syntaxbug.com © 2021 All Rights Reserved.