Design pattern [3.2] - how fragrant is JDK dynamic agent source code analysis?

Keywords: Javascript Design Pattern Dynamic Proxy

The previous article mentioned the agent mode: http://aphysia.cn/archives/dy...

So in retrospect, how did the agent model come from? Suppose you have a requirement:

Print the log before and after all controller classes in the system call methods.

Suppose the original code:

public class Person{
    public void method(){
        // Represent your business logic
        process();
    }
}

If I add printing methods to all classes, it must be unrealistic. If I have hundreds of such classes, they will crash. Moreover, there are too many duplicate codes, redundancy and coupling to one piece. If I don't log next time and do other things, all the hundreds of classes will be changed again.

public class Person{
    public void method(){
        log();
        // Represent your business logic
        process();
        log();
    }
}

Static proxy

How to write more beautiful? The static proxy comes out at this time. First, abstract the method into an interface:

public class IProxy(){
    public void method();
}

Let specific classes implement IProxy and write their own business logic, such as:

public class Person implements IProxy(){
    public void method(){
        // Represent your business logic
        process();
    }
}

Then get a proxy class to enhance the method:

public class PersonProxy implements IProxy{
    private IProxy target;
    public PersonProxy(IProxy target){
        this.target = target;
    }
    @Override
    public void method() {
        log();
        target.method();
        log();
    }
}

When calling, put the real object into the constructor of the proxy class to get a proxy class and enhance its methods. The advantage is that if I want to change it next time, I can change the proxy class without logging and doing other things. I don't have to change the methods of my target class everywhere. The disadvantage is still obvious. Which class should be enhanced, It seems unreasonable to write a proxy class for it.

Dynamic agent

How can he automatically generate proxy objects? This is what dynamic proxy does. It can dynamically generate the object of proxy class according to the class we provide.

The most important thing is to generate dynamically at runtime. As long as we pass in the information related to the class we want to enhance, such as the class object itself, class loader, class interface, etc., we can generate it without knowing whether it is class A, class B or class C in advance.

There are three main implementation methods for dynamic agent. Today, we focus on JDK dynamic agent:

  • JDK Proxy: use the official Proxy provided by JDK
  • Third party cglib proxy: create proxy objects using the Enhancer class of cglib
  • javassit: Javassist is an open source class library for analyzing, editing and creating Java bytecode. It was founded by Shigeru Chiba of the Department of mathematics and computer science of Tokyo University of technology.

JDK dynamic agent

Use steps

  1. Create a new interface
  2. Create a new class to implement the interface
  3. Create a proxy class to implement the java.lang.reflect.InvocationHandler interface

The code is as follows:

Iplaydao.java (play interface)

public interface IPlayDao {
    void play();
}

Studentdao.java (student class that implements the interface for shopping and playing)

public class StudentDao implements IPlayDao {
    @Override
    public void play() {
        System.out.println("I'm a student. I want to go out");
    }
}

MyProxy.java proxy class:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyProxy {
    private Object target;
    public MyProxy(Object target){
        this.target=target;
    }
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    // An interface may have many methods. If you need to target a method, you need to judge the method in the function
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("Start transaction 2");
                        // Execute target object method
                        Object returnValue = method.invoke(target, args);
                        System.out.println("Commit transaction 2");
                        return returnValue;
                    }
                }
        );
    }
}

Test class (Test.java)

public class Test {
    public static void main(String [] args){
        // Save the bytecode file that generates the proxy class
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        IPlayDao target =new StudentDao();
        System.out.println(target.getClass());
        IPlayDao proxy = (IPlayDao) new MyProxy(target).getProxyInstance();
        System.out.println(proxy.getClass());
        // Execution method [ proxy object ]
        proxy.play();

    }
}

Because of this code, we can save the bytecode file of the generated proxy class, In fact, you can also see from the output that the two objects are not the same class, and the proxy class is generated dynamically:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924001916.png)

### Source code analysis

Follow the source code step by step, first from the call place `Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)`: 

![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924002757.png)

Into the method,**Omit various exception handling**,Mainly left**Generate proxy class bytecode** as well as **Construct a new object through constructor reflection**: 
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    // Air judgment
    Objects.requireNonNull(h);
    // security check
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Find or generate proxy objects
     */
    Class<?> cl = getProxyClass0(loader, intfs);

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

    // Get constructor
    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;
            }
        });
    }
    // Reflection construction proxy object
    return cons.newInstance(new Object[]{h});
}
The above note says to find or generate proxy objects. Why is there a search? Because it is not generated every time, the generated proxy object will actually be cached. If not, it will be generated. Look at the source code `Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces)`: 
private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    if (interfaces.length> 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    // Call the cache of the cache proxy class to get the class loader
    return proxyClassCache.get(loader, interfaces);
}
If a proxy class defined by a given loader implementing a given interface exists, this simply returns a copy of the cache; Otherwise, it will pass ProxyClassFactory Create a proxy class,`proxyClassCache` It's actually a `weakCache`:

private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

When initializing, proxyClassCache Two properties are specified, one is `KeyFactory`, The other is `ProxyClassFactory`, Guess from the name `ProxyClassFactory` It is an agent factory:
public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                 BiFunction<K, P, V> valueFactory) {
    this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
    this.valueFactory = Objects.requireNonNull(valueFactory);
}
**Remember here subKeyFactory,It's actually incoming ProxyClassFactory**,That front `proxyClassCache.get(loader, interfaces);` How did it work?

![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924010200.png)

It's called from above `subKeyFactory.apply(key, parameter)`,this `subKeyFactory` It's actually from us `ProxyClassFactory`, Go inside and see:
private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // Generated proxy class prefix
    private static final String proxyClassNamePrefix = "$Proxy";

    // The counter of the name of the next generated proxy class is generally $Proxy0 and $Proxy1
    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 can load the class through the interface name
             */
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != intf) {
                throw new IllegalArgumentException(
                    intf + "is not visible from class loader");
            }
            /*
             * Determine the interface type
             */
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + "is not an interface");
            }
            /*
             * Determine whether to repeat
             */
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException(
                    "repeated interface:" + interfaceClass.getName());
            }
        }
        // Agent package name
        String proxyPkg = null;
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        /*
         * Record the packages of non-public proxy interfaces to define proxy classes in the same package. Verify that all non-public proxy interfaces are in the same package.
         */
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }

        if (proxyPkg == null) {
            // If there is no non-public proxy interface, use the com.sun.proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

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

        /*
         * Generates the specified proxy class. (here's the point!!!)
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            /*
             * The classformatererror here means that the parameters provided to create the proxy class have other invalid aspects (such as exceeding the virtual machine limit).
             */
            throw new IllegalArgumentException(e.toString());
        }
    }
}
The above calls a method to generate a proxy class. Let's take a look IDEA Decompiled Code:
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    // Generate file
    final byte[] var4 = var3.generateClassFile();
    // Determine whether to write to disk!!!
    if (saveGeneratedFiles) {
        // Turn on high permission write
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                try {
                    int var1 = var0.lastIndexOf(46);
                    Path var2;
                    if (var1> 0) {
                        Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                        Files.createDirectories(var3);
                        var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                    } else {
                        var2 = Paths.get(var0 + ".class");
                    }

                    Files.write(var2, var4, new OpenOption[0]);
                    return null;
                } catch (IOException var4x) {
                    throw new InternalError("I/O exception saving generated file:" + var4x);
                }
            }
        });
    }

    return var4;
}
The generation of proxy files is actually similar to what we think, just some hashCode(),toString(),equals(), Original method, agent method, etc.:

![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924011447.png)

This is consistent with the document we saw earlier:

![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924011628.png)

Then, the reason why our code should be set to write to disk is because of this variable, Write to disk operation is controlled:
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
**Why does it only support interface implementation instead of inheriting ordinary classes?**
Because the proxy class inherits Proxy Class and implements the interface, Java Multiple inheritance is not allowed, so it cannot proxy ordinary classes, and all proxy methods are obtained by reflection in static code blocks.

JDK The agent looks like a black box. In fact, every sentence of code has its reason. In fact, it is also dynamic in nature to dynamically generate proxy classes for our original classes.

The generated proxy class actually enhances the original class (for example `play()` Method), Called `super.h.invok()` Method, in fact, here `h` What is it?

`h` Is a parent class `h`, The parent class of the generated proxy class is `Proxy`,Proxy of `h`,That's what we brought in `InvocationHandler`:
public final void play() throws  {
    try {
        super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
In the generated code, what we call through reflection is actually the part of logic rewritten by ourselves, so there are enhanced functions. It has to be said that this design is really ingenious:

![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210925020135.png)

## How fragrant are dynamic agents

Dynamic agents are Java A very powerful feature of the language can be used to do some aspects, such as interceptors, login verification, etc., but it does not exist independently. No knowledge point can independently explain the power of the language. What is important is their combination.

To achieve powerful functions, dynamic agents generally need to cooperate with reflection and annotation, such as intercepting some requests, doing some login verification after interception, or logging functions. The most important point is that it can achieve enhancement on the premise of reducing coupling.

**[Author profile]**:   
Qin Huai, official account.**Qinhuai grocery store**]The author believes that the road of technology is not at the moment. The mountain is high and the river is long. Even if it is slow, it will not stop. Personal writing direction:`Java Source code analysis`,`JDBC`,`Mybatis`,`Spring`,`redis`,`Distributed`,`Sword finger Offer`,`LeetCode`I don't like the title party and fancy. I mostly write a series of articles. I can't guarantee that what I write is completely correct, but I guarantee that what I write has been practiced or searched for information. I hope to correct the omissions or mistakes.

[Sword finger Offer All questions PDF](http://aphysia.cn/archives/jianzhiofferpdf)

[2020 What did I write in?](http://aphysia.cn/archives/2020)

[Open source programming notes](https://damaer.github.io/Coding/#/)

Posted by shadysaiyan on Sat, 06 Nov 2021 11:29:09 -0700