Implementation and source code analysis of Java Dynamic agent

1: Implementation of static agent

Define interface

/**
 * @description:
 * A factory that processes mobile phones
 * @date: 2022/2/12
 * @author: linshujie
 */
public interface IPhoneFactory {
    /**
     * Customized design of mobile phone
     * @param design
     */
    void makePhoneDesign(String design);
}
/**
 * @description:
 * A factory that processes notebooks
 * @date: 2022/2/12
 * @author: linshujie
 */
public interface IComputerFactory {
    /**
     * Customized design of notebook
     * @param design
     */
    void makeComputerDesign(String design);
}

Implementation interface

public class ComputerCustomizedFactory implements IComputerFactory{
    @Override
    public void makeComputerDesign(String design) {
        System.out.println("After customizing the computer, the theme is:" + design);
    }
}
public class PhoneCustomizedFactory implements IPhoneFactory{
    @Override
    public void makePhoneDesign(String design) {
        System.out.println("After customizing the mobile phone, the theme is:" + design);
    }
}

Define proxy classes

/**
 * @description:
 * Static agent class, responsible for notebook business
 *
 * @date: 2022/2/12
 * @author: linshujie
 */
public class LaoWang implements IComputerFactory {
    public IComputerFactory computerFactory;

    public LaoWang(IComputerFactory computerFactory) {
        this.computerFactory = computerFactory;
    }

    private void preSalesService(){
        System.out.println("Free postage consultation!!!");
    }

    private void afterSalesService(){
        System.out.println("Three year warranty service!!!");
    }

    @Override
    public void makeComputerDesign(String design) {
        preSalesService();
        computerFactory.makeComputerDesign(design);
        afterSalesService();
    }
}

/**
 * @description:
 * Static agent, responsible for mobile phone business
 *
 * @date: 2022/2/12
 * @author: linshujie
 */
public class ZhangSan implements IPhoneFactory {
    public IPhoneFactory phoneFactory;

    public ZhangSan(IPhoneFactory phoneFactory) {
        this.phoneFactory = phoneFactory;
    }

    private void preSalesService(){
        System.out.println("Free postage consultation!!!");
    }

    private void afterSalesService(){
        System.out.println("Three year warranty service!!!");
    }

    @Override
    public void makePhoneDesign(String design) {
        preSalesService();
        phoneFactory.makePhoneDesign(design);
        afterSalesService();
    }
}

Define user classes

/**
 * @description:
 * user
 *
 * @date: 2022/2/12
 * @author: linshujie
 */
public class Client {
    public static void main(String[] args) {
        /**
         * 1:  Static proxy mode
         */
        PhoneCustomizedFactory phoneFactory = new PhoneCustomizedFactory();
        ZhangSan zhangSan = new ZhangSan(phoneFactory);
        zhangSan.makePhoneDesign("Spider Man Theme");

        ComputerCustomizedFactory computerCustomizedFactory = new ComputerCustomizedFactory();
        LaoWang laoWang = new LaoWang(computerCustomizedFactory);
        laoWang.makeComputerDesign("Iron Man Theme");
    }
}

Operation input is:

Free consultation!!!
After customizing the mobile phone, the theme is spider man theme
 Three year warranty service!!!
Free consultation!!!
After customizing the computer, the theme is: Iron Man theme
 Three year warranty service!!!

Disadvantages:

  • When we need to add agents to customize the services of watches or bracelets and other electronic products, we must create corresponding new agent classes to implement them. When a large number of proxy services need to be added later, it is obvious that the code will become very bloated!

2: Implementation of dynamic agent

Define a dynamic proxy class to replace the static proxy class: Zhangsan Class and Laowang class. And there is no need to manually create a specific proxy class, which is generated by the code.

/**
 * @description:
 * Custom service company class, as the entrance of agent. Dynamically generate proxy objects according to the incoming interface,
 * There is no need to manually new out the specific proxy object in the proxy
 * @date: 2022/2/12
 * @author: linshujie
 */
public class CustomizedCompany {
    /**
     * The factory to be imported is really responsible for the implementation of customization
     */
    private Object factory;

    public Object getFactory() {
        return factory;
    }

    public void setFactory(Object factory) {
        this.factory = factory;
    }

    /**
     * Enhanced by dynamic proxy object method
     * @return
     */
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                preSalesService();
                Object result = method.invoke(factory,args);
                afterSalesService();
                return result;
            }
        });
    }

    private void preSalesService(){
        System.out.println("Free consultation!!!");
    }

    private void afterSalesService(){
        System.out.println("Three year warranty service!!!");
    }
}

Next, execute in the Client class:

 /**
         * 2:  Dynamic agent mode
         */
        //Create a custom design company, responsible for various agent customization services, including mobile phones, computers, watches, tablets and other electronic devices
        CustomizedCompany company = new CustomizedCompany();

        //Mobile phone customization factory
        IPhoneFactory phoneCustomizedFactory = new PhoneCustomizedFactory();
        company.setFactory(phoneCustomizedFactory);
        //Dynamically generate employees responsible for mobile phone business
        IPhoneFactory employeePhone = (IPhoneFactory) company.getProxyInstance();
        employeePhone.makePhoneDesign("Superman theme");

        //Telephone customization factory
        IComputerFactory computerFactory = new ComputerCustomizedFactory();
        company.setFactory(computerFactory);
        //Dynamically generate employees responsible for notebook business
        IComputerFactory employeeComputer = (IComputerFactory) company.getProxyInstance();
        employeeComputer.makeComputerDesign("Captain America Theme");

Execution:

Free consultation!!!
After customizing the mobile phone, the theme is Superman theme
 Three year warranty service!!!
Free consultation!!!
After customizing the computer, the theme is: Captain America theme
 Three year warranty service!!!

Here we have a puzzle code:

 IPhoneFactory employeePhone = (IPhoneFactory) company.getProxyInstance();
        employeePhone.makePhoneDesign("Superman theme");

When executing these two lines of code, where is the bytecode file of the proxy object? We found the compiled bytecode folder

It's strange that I didn't find it. We know that when using dynamic proxy, the compiler will automatically generate proxy objects for us, and the generation of proxy objects must depend on its Class bytecode. So where does the Class bytecode of the dynamically generated proxy object exist? View objects through debug:

The generated object is Proxy0. Which bytecode is this object generated from?
Therefore, we need to go deep into the jdk source code to find out.

3: Implementation and source code analysis of dynamic agent

java provides a proxy class proxy java, starting with the newProxyInstance method:

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

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            ...

            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;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } ...
    }

We can see that getProxyClass0 returns a Class class, which is speculated to be the key code, and the following code is typically passed into the InvocationHandler interface through reflection to provide it to the proxy The newproxyinstance method callback is used.
Click in getProxyClass0 method:

 private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        ...

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

Here, take the class loader and class interface as parameters to get the proxy class cache, and click proxyclasscache Get method of get (loader, interfaces):

 public V get(K key, P parameter) {
        ...

        // create subKey and retrieve the possible Supplier<V> stored by that
        // subKey from valuesMap
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;
        
		...
    }

In the code supplier supplier = valuesmap get(subKey); It can be introduced that the supplier is the object in the cache. After the method, some business logic processing is carried out around the supplier. How to obtain the parameter subkey of this method?
In the code object subkey = objects requireNonNull(subKeyFactory.apply(key, parameter)); In the middle, let's turn it on
apply method:

   R apply(T t, U u);


In the implementation class, there is only the last one related to it. Click Open:
As can be seen from the name, this is an agent factory class

  private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                ...
            }
			...

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } ...
        }
    }

Here we find a clue:

        private static final String proxyClassNamePrefix = "$Proxy";

The name of "$Proxy" here is just a part of the Proxy object name we saw in debug earlier.

long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

The above two lines are the splicing operation of the full class name of the proxy class, that is, the "$Proxy0" object we saw during debug ging, and then:

 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);

The call of generateProxyClass method returns the byte array. We know that the class bytecode file is stored through the byte array. As for where to store it, there is no clear provision in the jvm specification. We can store it not only in the hard disk, but also in memory, or even in the database or network.
Click on the method proxygenerator Generateproxyclass, we enter a bytecode file, not a java file.
But we can basically know that our proxy class is generated dynamically here.
Then look at:

return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);

This line of code passes in parameters such as class loader and proxy class name, and returns defineClass0 to the superior method, that is, the operation of getting class class from cache we saw earlier. Click in and see that defineClass0 is a local method. Don't delve into it.
From here, we can know that the reason why we can't find the proxy class bytecode in the compiled class folder in the project is that the bytecode file generated in English through dynamic proxy is saved in the program cache.
This concludes the source code analysis of the dynamic agent.

4: Manually call jdk method to generate proxy class bytecode.

But I'm curious
How can we see the bytecode file generated by the compiler?
Can we directly use the code that generates proxy class bytecode in jdk? As follows:

 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);

Write a tool class:

public class ProxyUtils {
    public static void generateClassFile(Class clazz,String proxyName){
        /*ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);*/
        byte[] proxyClassFile =ProxyGenerator.generateProxyClass(
                proxyName, new Class[]{clazz});
        String paths = clazz.getResource(".").getPath();
        System.out.println(paths);
        FileOutputStream out = null;

        try {
            out = new FileOutputStream(paths+proxyName+".class");
            out.write(proxyClassFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Call:

    ProxyUtils.generateClassFile(phoneCustomizedFactory.getClass(),
                employeePhone.getClass().getSimpleName());
        ProxyUtils.generateClassFile(computerFactory.getClass(),
                employeeComputer.getClass().getSimpleName());


Click on the $Proxy0 bytecode. Through the decompile function of IDE, you can see that the proxy class is as follows:

public final class $Proxy0 extends Proxy implements PhoneCustomizedFactory {
    private static Method m1;
    private static Method m8;
    private static Method m2;
    private static Method m3;
    private static Method m5;
    private static Method m4;
    private static Method m7;
    private static Method m9;
    private static Method m0;
    private static Method m6;

    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 notify() throws  {
        try {
            super.h.invoke(this, m8, (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 makePhoneDesign(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void wait(long var1) throws InterruptedException {
        try {
            super.h.invoke(this, m5, new Object[]{var1});
        } catch (RuntimeException | InterruptedException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final void wait(long var1, int var3) throws InterruptedException {
        try {
            super.h.invoke(this, m4, new Object[]{var1, var3});
        } catch (RuntimeException | InterruptedException | Error var5) {
            throw var5;
        } catch (Throwable var6) {
            throw new UndeclaredThrowableException(var6);
        }
    }

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

    public final void notifyAll() throws  {
        try {
            super.h.invoke(this, m9, (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 wait() throws InterruptedException {
        try {
            super.h.invoke(this, m6, (Object[])null);
        } catch (RuntimeException | InterruptedException | 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"));
            m8 = Class.forName("com.lsj.prox_sample.PhoneCustomizedFactory").getMethod("notify");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.lsj.prox_sample.PhoneCustomizedFactory").getMethod("makePhoneDesign", Class.forName("java.lang.String"));
            m5 = Class.forName("com.lsj.prox_sample.PhoneCustomizedFactory").getMethod("wait", Long.TYPE);
            m4 = Class.forName("com.lsj.prox_sample.PhoneCustomizedFactory").getMethod("wait", Long.TYPE, Integer.TYPE);
            m7 = Class.forName("com.lsj.prox_sample.PhoneCustomizedFactory").getMethod("getClass");
            m9 = Class.forName("com.lsj.prox_sample.PhoneCustomizedFactory").getMethod("notifyAll");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m6 = Class.forName("com.lsj.prox_sample.PhoneCustomizedFactory").getMethod("wait");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

In this way, the puzzle is completely solved.

5: Dynamic agent in Retrofit framework

After mastering the implementation principle and source code execution process of dynamic agent, and then looking at the open source framework, you will find that many dynamic agents in third-party frameworks use this way.
For famous examples, such as Retrofit, we only need to define the interface, and Retrofit can cooperate with OkHttp through dynamic agent to implement the specific implementation of the interface.

Keywords: Java Dynamic Proxy

Added by sublevel4 on Sat, 12 Feb 2022 18:46:41 +0200