[advanced path] dynamic proxy and bytecode generation

Posted by le007 on Wed, 05 Jan 2022 19:53:14 +0100

During this period, I changed my job. Because I went to a foreign enterprise, my requirements for English suddenly increased. Now I live by Google translation every day. In meetings, I often encounter words I don't understand. I often need to write down the pronunciation, and then slowly find the corresponding words according to the context. My life is very interesting. Therefore, most of the self charging time is used to learn English, so the pace of updating will be very slow during this period~

For most Java programmers, we often use bytecode generation and dynamic proxy technology, such as AOP framework woven at compile time, Bean organization and management in Spring, or JSP compiler of Web server. In short, we have unconsciously used a lot of these technologies.

The dynamic agent mentioned in the dynamic agent is a static agent that actually writes an agent class based on Java code. In comparison, its advantage is that it can determine the agent behavior before knowing the original class and interface, which can be used flexibly in different application scenarios. At the same time, it can also reduce the number of lines of code and make your code more beautiful and concise.

1, Dynamic agent

Here is a simple method to implement a dynamic agent. If you want to see the method based on AOP and annotation, you can go to my previous article, which is also very detailed [advanced road] user defined annotation introduction and Practice.

public class DynamicTest {
    public static void main(String[] args) {
        IPayment pay = (IPayment) new BankDynamicProxy().bind(new Alipay());
        pay.payment();
    }

    interface IPayment {
        void payment();
    }

    static class Alipay implements IPayment {
        @Override
        public void payment() {
            System.out.println("Use Alipay to payment");
        }
    }

    static class BankDynamicProxy implements InvocationHandler {
        Object dynamicProxy;

        Object bind(Object dynamicProxy) {
            this.dynamicProxy = dynamicProxy;
            return Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader()
                    , dynamicProxy.getClass().getInterfaces(), this);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("access Bank Api");
            return method.invoke(dynamicProxy, args);
        }
    }
}

The logic of this dynamic proxy method is very simple, that is, when requesting a bank interface with Alipay payment. Through this method, we can use the debug method to see some column operations such as program verification, optimization, caching, bytecode generation, class loading, etc

But this time we don't have to explore the whole process, we just need to understand the operation of bytecode generation.

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

This method will produce a bytecode byte [] array describing the proxy class at runtime.

It's too troublesome to see in debug. We can generate a bytecode file and view the specific content through decompilation.

2, Bytecode generation

Just add the following method to the main method of the code to generate a method named P r o x y 0. c l a s s of generation reason class writing piece . When however , change become Proxy0.class. Of course, replace it with Proxy0.class. Of course, change to Nanju No problem.

public static void main(String[] args) {

    byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", DynamicTest.class.getInterfaces());
    String path = "D:\\temp\\$Proxy0.class";
    try (FileOutputStream fos = new FileOutputStream(path)) {
        fos.write(classFile);
        fos.flush();
        System.out.println("Agent class file written successfully");
    } catch (Exception e) {
        System.out.println("file written fail");
    }
}

Then we directly drag the class file to the IntelliJ IDEA tool through the simplest method, and IntelliJ automatically decompiles it into Java files.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy {
    private static Method m1;
    private static Method m2;
    private static Method m0;

    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 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);
        }
    }

    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");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

From the decompiled code of this dynamic proxy class, it can be seen that it generates corresponding implementations for the methods such as equals(),toString(), hashCode() inherited from Object, and uniformly calls Java lang.reflect. The invoke() Method in the InvocationHandler Object is only different from the Method method Method in the parameters passed in. Therefore, no matter what Method the dynamic proxy uses, it still executes the logic in InvocationHandler.

The generateProxyClass() method splices bytecodes through the specification of the Class file, but for program code, such splicing consumes resources and can only produce highly templated code. Compared with such generation, the off the shelf bytecode library is more suitable for production practice.

Students in need can add my official account. The latest articles in the future are in the first place, and I can also find my mind map.

Topics: Java jvm Dynamic Proxy