Java Dynamic agent -- principle explanation & source code analysis

Examples of using dynamic agents

When it comes to Java's dynamic agent, I believe many developers are very familiar with it. However, in order to better explain and analyze the principle and source code of dynamic agent, let's first understand the actual use examples of dynamic agent:

Now there is a scenario:

  • Star Michel has two skills, singing and dancing
  • Stars will have an agent. Their job is to help stars deal with all kinds of other affairs except singing and dancing
  • The person in charge of the concert needs to invite star Michel to sing and dance on site, and then contact Michel's agent to inform the request

The following is a code demonstration. First, create a star interface and define the singing and dancing method:

public interface Starter {

    void sing();

    void dance();
}

Create specific star Michel and define specific singing and dancing logic:

public class Michel implements Starter{

    @Override
    public void sing() {
        System.out.println("Michel singing...");
    }

    @Override
    public void dance() {
        System.out.println("Michel dancing...");
    }
}

Create Broker:

public class Agent implements InvocationHandler {

    private Starter starter;

    public Agent(Starter starter) {
        this.starter = starter; // Set proxy star
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("I'll act as agent Starter...");
        Object result = method.invoke(starter, args);
        System.out.println("The show is over. Thank you...");
        return result;
    }
}

Finally, let the process start:

public class Main {
    public static void main(String[] args) {
        Starter michel = new Michel(); // Create a star
        Agent agent = new Agent(michel); // Create broker
        // The relationship between agent and star Michel is used to generate the final agent object
        Starter michelProxy = (Starter) Proxy.newProxyInstance(michel.getClass().getClassLoader(), // Principal (Michel)
                michel.getClass().getInterfaces(), // Which interfaces (singing and dancing) are represented
                agent); // Agent (broker)
        // Let the proxy object sing and dance
        michelProxy.sing();
        michelProxy.dance();
    }
}

Execution result:

It can be seen that after the above code, the program can achieve the effect of agent.

Principle & source code analysis

The above code example is believed to be well understood by everyone, but the principle is not so simple on the surface. First, let's ask a few questions:

  1. What is the proxy object of michelProxy? How did it come from? Why can it be strongly converted to Starter type?
  2. Finally, the sing and dance methods of michelProxy are executed. Why does it call the invoke method of agent? What is the relationship between the agent object and the agent?

In order to understand the first question, we need to print proxy The return value of newproxyinstance method, that is, the specific information of michelProxy:

public class Main {
    public static void main(String[] args) {
        Starter michel = new Michel();
        Agent agent = new Agent(michel);
        
        // The relationship between agent and star Michel is used to generate the final agent object
        Starter michelProxy = (Starter) Proxy.newProxyInstance(michel.getClass().getClassLoader(), // Principal (Michel)
                michel.getClass().getInterfaces(), // Which interfaces (singing and dancing) are represented
                agent); // Agent (broker)

		// Print information
        System.out.println("Type of proxy object:" + michelProxy.getClass());
        System.out.println("Parent type of proxy object:" + michelProxy.getClass().getSuperclass());
        System.out.print("Interface implemented by proxy object:");
        for (Class<?> anInterface : michelProxy.getClass().getInterfaces()) {
            System.out.print(anInterface.getName());
        }

        // Let the proxy object sing and dance
//        michelProxy.sing();
//        michelProxy.dance();
    }
}

The printing results are as follows:

From the above results, we can see that the so-called Proxy object is actually an instance object of the $Proxy0 class, which inherits the Proxy parent class and implements our customized Starter interface

In order to further understand the proxy object, we need to open the $Proxy0 class file to see the specific content. At this time, you will find that the class file $Proxy0 cannot be found in the project? In fact, that's because the class file of the proxy object is generated during the running of the program. It exists in memory, so we can't find it in the file system or disk.

Is there any way to see such documents? The answer is yes. We only need to add one sentence of code in the main method: system getProperties(). put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”); (output the class file of proxy object $Proxy0 from memory to hard disk). Then execute the main method again, and you can see it in the following directory:

Then, open the $Proxy0 class file:

public final class $Proxy0 extends Proxy implements Starter { // Inherit the parent class Proxy and implement the user-defined interface Starter

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

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m4 = Class.forName("mike.test.Starter").getMethod("sing");
            m3 = Class.forName("mike.test.Starter").getMethod("dance");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
    
    public $Proxy0(InvocationHandler var1) throws  { // The constructor receives the Proxy and calls the Proxy parent constructor
        super(var1);
    }
	
    public final void sing() throws  { // The implementation of the singing interface actually calls the invoke method of the agent (the h attribute is actually the agent we created, which will be explained in the source code analysis below)
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    
    public final void dance() throws  { // Implementing the dance interface actually calls the invoke method of the agent
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
	
	// The following is the coverage of the general methods of the Object class, which will not be repeated
    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 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 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);
        }
    }
}

I believe that you have a comprehensive understanding of the class $Proxy0, and also solve why the proxy object can enhance the original interface method (because the sing and dance methods have been wrapped, and the invoke method of the proxy is directly called)

So far, we have understood the specific content of the proxy object. Next, we will further understand the generation logic of the proxy object, focusing on proxy Newproxyinstance method:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
{
    // Verify the parameters and the access rights of the interface
    Objects.requireNonNull(h);
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass0(loader, intfs); // Generate proxy object $Proxy0 class object (the method is very long, not detailed, and you can study it yourself if you are interested)

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl); // Verify the interface permission of proxy object class
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams); // Gets the parameterized constructor method of the proxy object class
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) { // If the proxy object class is not Public, the access permission is open
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h}); // Call the parameterized constructor of the proxy object class, pass in the proxy, and generate and return the proxy object instance
    } 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);
    }
}

When the above methods are disassembled, there are only three core code statements:

Class<?> cl = getProxyClass0(loader, intfs); // Generate proxy object $Proxy0 class object (the method is very long, not detailed, and you can study it yourself if you are interested)

final Constructor<?> cons = cl.getConstructor(constructorParams); // Gets the parameterized constructor method of the proxy object class

return cons.newInstance(new Object[]{h}); // Call the parameterized constructor of the proxy object class, pass in the proxy, and generate and return the proxy object instance

After the proxy class object is obtained, the parameterized constructor is obtained through it. The parameterized constructor depends on constructorParams:

private static final Class<?>[] constructorParams = { InvocationHandler.class };

As the attribute of Proxy class, constructorParams is actually a fixed value {InvocationHandler.class} (so we can easily think of the implementation class of the interface - agent). After obtaining the corresponding constructor, use reflection to pass in the agent to create and return the agent object:

public $Proxy0(InvocationHandler var1) throws  { // var1 is actually an agent object
    super(var1); // The corresponding parent class of the constructor call Proxy
}
protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    this.h = h; // Finally, assign the agent as the attribute h of the agent object
}

So far, the truth has been revealed. The principle of Java's dynamic Proxy mechanism & source code analysis is completed.

Keywords: Java Back-end Dynamic Proxy Proxy

Added by jsschmitt on Wed, 09 Feb 2022 05:57:03 +0200