JDK dynamic proxy usage and related source code interpretation

Dynamic agent

summary

Previously, when implementing static Proxy, you need to define and generate Proxy classes according to the implemented interface. The JDK dynamic Proxy uses the methods in the java.lang.reflect.Proxy class. If you want to generate a class Proxy class, you only need to customize a handler that handles the logic before and after the execution of the method, and call the method of generating the Proxy class in the Proxy class to get the Proxy class. You don't need to write your own Proxy class for different interfaces.

give an example

Define the interface implemented by the proxy class and the proxy class

interface Human {
    String getBelief();

    void eat(String food);
}

Defines the SuperMan of the proxied class

class SuperMan implements Human {

    @Override
    public String getBelief() {
        return "I believe I can fly!";
    }

    @Override
    public void eat(String food) {
        System.out.println("I like to eat " + food);
    }
}

Create a general proxy class production factory, pass in an object, and return its proxy class

class ProxyFactory {
    // Call this method to return an object of proxy class. Solve 1
    public static Object getProxyInstance(Object obj) { // obj: object of the proxied class
        Object instance = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
                // This is a user-defined class that implements the InvocationHandler interface. It is a function interface
                // There is only the invoke method
                (proxy, method, args) -> {
                    System.out.println("Before method call......");
                    // Call the method of the proxied object through reflection
                    Object o = method.invoke(obj, args);
                    System.out.println("After method call......");
                    return o;
                });
        return instance;
    }
}

The last parameter in newProxyInstance() is an object that implements the InvocationHandler interface. InvocationHandler is a function interface. The specific source code is as follows

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

When the method of the proxy class is called, the proxy class will call the invoke method of the handler to execute the relevant logic. In this way, we can weave the logic we want to execute before and after the proxy class executes the method (method.invoke(obj, args)).

test

public class ProxyTest {
    @Test
    public void test() {
        SuperMan superMan = new SuperMan();
        // Get proxy object
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        // Calling the method of the interface will automatically call the invoke method in the handler and dynamically call the method with the same name
        proxyInstance.eat("apple");
    }
}
  1. results of enforcement

    It can be seen that the user-defined logic is also executed before and after the proxy object method is executed

Source code

The source code is divided into two parts: the Proxy generation method and the generated Proxy execution target method
Note: the JDK version corresponding to the source code of this article is 15

Generate proxy class source code

  1. Enter Proxy.newProxyInstance() method

    /**
     * Returns the proxy class that implements the given interface
     */
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        Objects.requireNonNull(h);
    
        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();
    
        /*
         * Find the constructor used to generate the proxy class
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
    
        return newProxyInstance(caller, cons, h);
    }
    

    The key statement is the penultimate statement. Enter this method

  2. Proxy.getProxyConstructor()

    /**
     * Returns the constructor of the proxy class
     */
    private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                      ClassLoader loader,
                                                      Class<?>... interfaces)
    {
        // Optimize the case where there is only one interface
        if (interfaces.length == 1) {
            Class<?> intf = interfaces[0];
            if (caller != null) {
                checkProxyAccess(caller, loader, intf);
            }
            // Insert a new proxyBuilder into the proxy cache
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        } else {
            // Make a deep copy of the interface array
            final Class<?>[] intfsArray = interfaces.clone();
            if (caller != null) {
                checkProxyAccess(caller, loader, intfsArray);
            }
            // Convert array to list
            final List<Class<?>> intfs = Arrays.asList(intfsArray);
            return proxyCache.sub(intfs).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        }
    }
    

    There is a property proxyCache in the Proxy class, which is a cache with a Proxy class constructor. In line 17, ld is the class loader and clv is the class loader value. Here, the key of clv is the interface Human implemented by the Proxy class

    After the anonymous object new of ProxyBuilder comes out, it enters its build() method

  3. Proxy.ProxyBuilder.build() method

    /**
     * Generate the Class class of the proxy Class and return the required constructor
     */
    Constructor<?> build() {
        // Class class that defines the proxy class
        Class<?> proxyClass = defineProxyClass(module, interfaces);
        final Constructor<?> cons;
        try {
            cons = proxyClass.getConstructor(constructorParams);
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                cons.setAccessible(true);
                return null;
            }
        });
        return cons;
    }
    

    Line 6 is the statement to generate the proxy Class. Enter this method

  4. java.lang.reflect.Proxy.ProxyBuilder#defineProxyClass

    /**
     * Generate Class class of proxy Class
     */
    private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
        String proxyPkg = null;     // The package to which the proxy class belongs
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    
        /*
         * Record the packages of all non-public interfaces to ensure that the packages of the proxy classes and non-public interfaces generated later are the same
         * same. At the same time, to verify that all non-public interfaces are in the same package (if not in the same package,
         * The generated proxy class cannot access these interfaces)
         */
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;  // non-public, final
                String pkg = intf.getPackageName();
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                }
            }
        }
    
        if (proxyPkg == null) {
            // If all interfaces are public, the default package is used
            proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
                                   : PROXY_PACKAGE_PREFIX;
        } else if (proxyPkg.isEmpty() && m.isNamed()) {
            // If the package name is an empty string and the module is not unnamed, an exception is thrown
            throw new IllegalArgumentException(
                    "Unnamed package cannot be added to " + m);
        }
    
        if (m.isNamed()) {
            if (!m.getDescriptor().packages().contains(proxyPkg)) {
                throw new InternalError(proxyPkg + " not exist in " + m.getName());
            }
        }
    
        /*
         * The name of the generated proxy class
         */
        // Because a new proxy class is to be generated, the global proxy class serial number is increased by one
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg.isEmpty()
                                ? proxyClassNamePrefix + num
                                : proxyPkg + "." + proxyClassNamePrefix + num;
    
        ClassLoader loader = getLoader(m);
        trace(proxyName, m, loader, interfaces);
    
        /*
         * Generate proxy class
         */
        // Generate bytecode of proxy class
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(loader, proxyName, interfaces, accessFlags);
        try {
            // Define the Class according to the generated bytecode
            Class<?> pc = JLA.defineClass(loader, proxyName, proxyClassFile,
                                          null, "__dynamic_proxy__");
            // Add this proxy class to the proxy class cache
            reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
            return pc;
        } 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 new IllegalArgumentException(e.toString());
        }
    }
    

    First, determine whether all non-public interfaces are under the same package to ensure that the proxy classes generated later are also under the package (otherwise, these interfaces cannot be accessed). Then generate the name of the proxy class. Here, the generation rule of the name of the proxy class is the unified prefix of the proxy class + serial number. Each time a proxy class is generated, add one with cas operation.

    Line 59 is the statement that generates the bytecode of the proxy class. It calls the method of the ProxyGenerator class. Let's take a look at the class diagram of this class first

    It inherits ClassWriter. It is judged here that it has the function of generating class bytecode files

  5. java.lang.reflect.ProxyGenerator#generateProxyClass

    static byte[] generateProxyClass(ClassLoader loader,
                                     final String name,	// The name of the previously generated proxy class
                                     List<Class<?>> interfaces,
                                     int accessFlags) {
        // new is a proxy class generator
        ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags);
        // Generate bytecode file of proxy class
        final byte[] classFile = gen.generateClassFile();
    
        // ...
    
        return classFile;
    }
    

    The method called by the statement entering line 8

  6. java.lang.reflect.ProxyGenerator#generateClassFile

    private byte[] generateClassFile() {
        visit(V14, accessFlags, dotToSlash(className), null,
                JLR_PROXY, typeNames(interfaces));
    
        /*
         * Add some common methods to the proxy class: hashCode, equals, toString
         * This step is performed before the interface method is generated because if the interface method overrides the methods in the three Object classes
         * You can achieve coverage
         */
        addProxyMethod(hashCodeMethod);
        addProxyMethod(equalsMethod);
        addProxyMethod(toStringMethod);
    
        /*
         * Add all methods of all interfaces
         */
        for (Class<?> intf : interfaces) {
            for (Method m : intf.getMethods()) {
                if (!Modifier.isStatic(m.getModifiers())) {
                    addProxyMethod(m, intf);
                }
            }
        }
    
        /*
         * Check whether the return types of methods with the same name and parameters conflict
         */
        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            checkReturnTypes(sigmethods);
        }
    
        // Generation construction method
        generateConstructor();
    
        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            for (ProxyMethod pm : sigmethods) {
                // Add the static modifier to the Method object in the proxy class
                visitField(Modifier.PRIVATE | Modifier.STATIC, pm.methodFieldName,
                        LJLR_METHOD, null, null);
    
                // Generate the code for the proxy class to execute this method
                pm.generateMethod(this, className);
            }
        }
    
        // The static code block of the generated class is used to initialize those static Method objects
        generateStaticInitializer();
    
        return toByteArray();
    }
    

    First, add the default hashcode, equals and toString methods, and then add all the methods of the interface (in fact, add the Method object to the proxy class). The constructor is then generated. Since the Method added before only adds Method member variables to the class (an example will be given later), adding static modifiers to these variables also needs to generate Method code for the proxy class (code can be executed). Finally, a static code block is added to initialize the static Method objects.

  7. Go back to step 4, line 60 of the code. After getting the bytecode, generate the proxy Class, and then add the proxy Class to the cache.

  8. Go back to step 3. After getting the proxy class, get its constructor and return

  9. Return to step 2 and return to the resulting constructor

  10. Returning to the last sentence of step 1, executing the newProxyInstance() method is actually executing the following statement

    return cons.newInstance(new Object[]{h});
    

    This constructor has only one input parameter, which is the handler of the previously customized invoke method

Disassembly of generated bytecode file

final class $Proxy0 extends Proxy implements Human {
    // The Method object mentioned earlier
    private static Method m0;
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m4;

    // Constructor, the input parameter is of type InvocaitonHandler
    public $Proxy0(InvocationHandler param1) {
        super(var1);
    }

    public final int hashCode() {
        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 boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() {
        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 String getBelief() {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

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

    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("example.Human").getMethod("getBelief");
            m4 = Class.forName("example.Human").getMethod("eat", Class.forName("java.lang.String"));
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

It can be seen that there are static member variables in the proxy class, all of which are Method types. When the Method is called, these Method objects are passed into the handler. These static methods will be initialized in the static code block and obtained by reflection. When a Method is called, you can pass the proxy class, the corresponding Method object and the corresponding parameters into the handler.

Keywords: Java source code

Added by RonDahl on Wed, 01 Dec 2021 16:50:01 +0200