You must know the JDK dynamic agent and CGLIB dynamic agent

Posted by Sydok on Mon, 06 Apr 2020 10:07:24 +0200

When we read the source code of some Java frameworks, we often see the use of dynamic proxy mechanism, which can unconsciously enhance the methods of existing code, so that the code has a better expansibility.
Through the static agent, JDK dynamic agent, CGLIB dynamic agent to analyze this paper.

Static proxy

Static proxy is that before the program runs, the bytecode. Class of proxy class has been compiled. Generally, a static proxy class only proxy one target class, and both proxy class and target class implement the same interface.
Next, we will analyze what is static proxy through demo. Currently, we create an animation interface, which contains call function.

package top.ytao.demo.proxy;

/**
 * Created by YangTao
 */
public interface Animal {

    void call();

}

Create the target class Cat and realize the Animal interface at the same time. The following is the realization of Cat calling.

package top.ytao.demo.proxy;

/**
 * Created by YangTao
 */
public class Cat implements Animal {

    @Override
    public void call() {
        System.out.println("cat ~");
    }
}

Because Cat is hungry before calling, we need to indicate that it is hungry before the target object method Cat call, which is to use static agent to realize Cat hunger and then make a call.

package top.ytao.demo.proxy.jdk;

import top.ytao.demo.proxy.Animal;

/**
 * Created by YangTao
 */
public class StaticProxyAnimal implements Animal {

    private Animal impl;

    public StaticProxyAnimal(Animal impl) {
        this.impl = impl;
    }

    @Override
    public void call() {
        System.out.println("Cat hunger");
        impl.call();
    }
}

The behavior of cat starvation and barking is realized by calling static agents.

public class Main {

    @Test
    public void staticProxy(){
        Animal staticProxy = new StaticProxyAnimal(new Cat());
        staticProxy.call();
    }
}  

results of enforcement

The relationship among agent class, target class and interface is shown in the figure:

In the above content, we can see that the static proxy is realized by holding the target class object and then calling the method of the target class.
Although static agent implements agent, it has obvious shortcomings in some cases:

  1. When we add a method to the Animal interface, it is not only necessary to add the implementation of this method to implement Cat class, but also because the agent class implements the Animal interface, so the agent class must also implement the new method of Animal, which is not good for maintenance when the project scale is large.
  2. The proxy class implementation of animal call is set for the object of Cat target class. If you need to add the proxy of Dog target class, you must implement a corresponding proxy class for Dog class, which makes the reuse of proxy class unfriendly and too many proxy classes cumbersome to maintain.

The above problems are solved friendly in JDk dynamic agent.

JDK dynamic agent

The main difference between dynamic agent class and static agent class is that the bytecode of agent class is not generated before the program runs, but created automatically in the virtual machine when the program runs.
Continue to implement JDK dynamic proxy with Cat class and Animal interface above.

Implement the InvocationHandler interface

The JDK dynamic proxy class must implement the java.lang.reflect.InvocationHandler interface in the reflection package, where there is only one invoker method:

In the InvocationHandlerාinvoker, the method of the target class being proxied must be called, otherwise the proxy cannot be realized. Here is the code to implement the InvocationHandler.

/**
 * Created by YangTao
 */
public class TargetInvoker implements InvocationHandler {
    // Target class held in agent
    private Object target;

    public TargetInvoker(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("jdk Before agent execution");
        Object result = method.invoke(target, args);
        System.out.println("jdk After agent execution");
        return result;
    }
}

When implementing invocationhandler ා invoker, there are three parameters in the method:

  • Proxy the proxy object of the target object, which is the real proxy object.
  • Method the method of executing the target class
  • args parameters of the method executing the target class

Create JDK dynamic proxy class

Creating an instance of the JDK dynamic proxy class also uses the java.lang.reflect.Proxy class in the reflection package. Create by calling the proxy ා newproxyinstance static method.

/**
 *
 * Created by YangTao
 */
public class DynamicProxyAnimal {

    public static Object getProxy(Object target) throws Exception {
        Object proxy = Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // Specifies the class load of the target class
                target.getClass().getInterfaces(),  // The interface that the agent needs to implement, multiple can be specified, this is an array
                new TargetInvoker(target)   // Proxy object processor
        );
        return proxy;
    }

}

Three parameters in proxy ා newproxyinstance (ClassLoader loader, class <? > [] interfaces, InvocationHandler h):

  • Loader class loader for loading agent objects
  • interfaces the interface implemented by the proxy object is the same as that implemented by the target object
  • h the handler that handles the logic of the proxy object, the InvocationHandler implementation class above.

Finally, the dynamic proxy is implemented

public class Main {

    @Test
    public void dynamicProxy() throws Exception {
        Cat cat = new Cat();
        Animal proxy = (Animal) DynamicProxyAnimal.getProxy(cat);
        proxy.call();
    }
}    

Execution result:

Through the above code, there are two questions: how is the proxy class created and how does the proxy class call methods?

Analysis

Source code analysis from the proxy ා newproxyinstance entry:

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

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    // Find or build the specified proxy class
    Class<?> cl = getProxyClass0(loader, intfs);

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

        // Get the constructor of the agent
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        // Handles the proxy class modifier so that it can be accessed
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        // Create proxy class instantiation
        return cons.newInstance(new Object[]{h});
    } 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);
    }
}

Get the proxy class in the newProxyInstance method. If the function of the class cannot be accessed, make it accessible, and finally instantiate the proxy class. The core of this code is to get the getProxyClass0 method of the proxy class.

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

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    // There can be no more than 65535 interfaces in the implementation class
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // Get proxy class
    return proxyClassCache.get(loader, interfaces);
}

If the specified proxy class exists in the proxyClassCache, it is obtained directly from the cache; if not, it is created through ProxyClassFactory.
As for why the maximum interface is 65535, this is defined by bytecode file structure and Java virtual machine, which can be understood by studying bytecode file.

Enter proxyclasscache get to get the proxy class:

Continue to enter the factory get view,

Finally, go to proxyclassfactory ා apply, where the proxy class is created.

private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
    // All proxy class names are named with this prefix
    private static final String proxyClassNamePrefix = "$Proxy";

    // Number of proxy class name
    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 agent and the target object implement the same interface
            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");
            }
            
            // Verify that interfaceClass is an interface
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }
            
            // Determine whether the current interfaceClass is repeated
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
        }

        // Package name of agent class
        String proxyPkg = null;     
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        // Record the package of the non public modifier proxy interface so that the generated proxy class is under the same package as it
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                // Get interface class name
                String name = intf.getName();
                // Remove the name of the interface and get the package name of the package
                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 the interface class is public decorated, use the package name com.sun.proxy
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        // Create agent class name
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        // Generate proxy class bytecode file
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            // Load bytecode to generate the specified proxy object
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
    }
}

The above is to create a bytecode process. By checking the properties of the interface, determine the package name and name rules generated by the bytecode file of the proxy class, and then load the bytecode to obtain the proxy instance. The bytecode file generated by operation generates a specific bytecode file in proxygenerator ා generateproxyclass. The bytecode operation will not be explained in detail here.
For the generated bytecode file, we can decompile and view the class information by saving it locally. There are two ways to save the generated bytecode file: setting the jvm parameters or writing the generated byte [] to the file.

According to the proxygenerator ා generateproxyclass method in the above figure, it is controlled by the value of the saveGeneratedFiles attribute. The value source of this attribute is:

private static final boolean saveGeneratedFiles = ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();

Therefore, the generated proxy class bytecode is saved locally by setting.

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

To decompile and view the generated proxy class:

The generated Proxy class inherits the Proxy and implements the Animal interface, and calls the call method to implement the targetinvoker ා invoker execution by calling the InvocationHandler held by the Proxy.

CGLIB dynamic agent

The implementation mechanism of CGLIB dynamic proxy is to generate the subclass of the target class, which is implemented by calling the method of the parent class (target class), and then enhanced in proxy when calling the method of the parent class.

Implement MethodInterceptor interface

Compared with the implementation of JDK dynamic proxy, CGLIB dynamic proxy does not need to implement the same interface as the target class, but implements the proxy by means of method interception. The code is as follows. First, the method intercepts the interface net.sf.cglib.proxy.MethodInterceptor.

/**
 * Created by YangTao
 */
public class TargetInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CGLIB Before calling");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("CGLIB After calling");
        return result;
    }
}

Call the method of the target class through the method interception interface, and then enhance the intercepted method. There are four parameters in the intercept method of the method interceptor interface

  • obj proxy class object
  • Method the method currently intercepted by the agent
  • args intercept method parameters
  • The proxy method of the proxy class corresponding to the target class

Create CGLIB dynamic proxy class

Create CGLIB dynamic proxy class using net.sf.cglib.proxy.Enhancer class. It is the core class in CGLIB dynamic proxy. First, create a simple proxy class:

/**
 * Created by YangTao
 */
public class CglibProxy {

    public static Object getProxy(Class<?> clazz){
        Enhancer enhancer = new Enhancer();
        // Set class load
        enhancer.setClassLoader(clazz.getClassLoader());
        // Set proxy class
        enhancer.setSuperclass(clazz);
        // Set method interceptor
        enhancer.setCallback(new TargetInterceptor());
        // Create proxy class
        return enhancer.create();
    }

}

A proxy class can be implemented by setting the information of the proxy class and the callback execution logic of the methods intercepted by the proxy class.
To implement CGLIB dynamic proxy call:

public class Main {

    @Test
    public void dynamicProxy() throws Exception {
        Animal cat = (Animal) CglibProxy.getProxy(Cat.class);
        cat.call();
    }
}

Execution result:

This is how CGLIB dynamic agent is implemented in simple application. However, Enhancer is commonly used and featured in the use of callback filter. When it intercepts the methods of the target object, it can selectively perform method interception, that is, to select the enhanced processing of the agent method. The net.sf.cglib.proxy.CallbackFilter interface is needed to use this function.
Now add a method interception implementation:

/**
 * Created by YangTao
 */
public class TargetInterceptor2 implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CGLIB Before calling TargetInterceptor2");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("CGLIB After calling TargetInterceptor2");
        return result;
    }
}

Then add the hobby method in Cat. Because CGLIB proxy does not need to implement the interface, it can directly proxy ordinary classes, so there is no need to add the method in the Animal interface:

package top.ytao.demo.proxy;

/**
 * Created by YangTao
 */
public class Cat implements Animal {

    @Override
    public void call() {
        System.out.println("cat ~");
    }
    
    public void hobby(){
        System.out.println("fish ~");
    }
}

Implement callback filter

/**
 * Created by YangTao
 */
public class TargetCallbackFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if ("hobby".equals(method.getName()))
            return 1;
        else
            return 0;
    }
}

To demonstrate calling different method interceptors, in the Enhancer setting, use Enhancer ා setcallbacks to set multiple method interceptors. The parameter is an array. The number returned by targetcallbackfilter ා accept is the index of the array, which determines the callback selector to be called.

/**
 * Created by YangTao
 */
public class CglibProxy {

    public static Object getProxy(Class<?> clazz){
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(clazz.getClassLoader());
        enhancer.setSuperclass(clazz);
        enhancer.setCallbacks(new Callback[]{new TargetInterceptor(), new TargetInterceptor2()});
        enhancer.setCallbackFilter(new TargetCallbackFilter());
        return enhancer.create();
    }

}

According to the code implementation logic, the call method will call the TargetInterceptor class, and the hobby class will call the TargetInterceptor2 class. The execution result is as follows:

The implementation principle of CGLIB is to set the class information to be proxied into Enhancer, and then use the configuration information to generate proxy class objects in Enhancer ා create. Generating classes are generated by using ASM, which is not analyzed in this paper. If you don't pay attention to the operation principle of ASM, it's easier to read only the processing principle of CGLIB. Here, we mainly look at the generated proxy class bytecode file by setting

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\xxx");

The generated bytes can be saved in the F:\xxx folder

You can see through Decompilation

The proxy class inherits the target class Cat, and loads the two methods interceptor into the proxy class. It distinguishes the Callbacks subscript as the suffix of the variable name, and finally calls the intercept in the specified method interceptor to achieve the final execution result of the agent.
It should be noted that CGLIB dynamic proxy can't proxy final decorated classes and methods.

Last

By decompiling the generated JDK proxy class and CGLIB proxy class, we can see the implementation of two different mechanisms:
JDK dynamic proxy implements the interface of the target class, and then passes the target class in as a parameter when constructing the dynamic proxy, so that the proxy object holds the target object, and then realizes the operation of the dynamic proxy through the InvocationHandler of the proxy object.
CGLIB dynamic proxy is to generate the subclass of the target class by configuring the target class information and using the ASM bytecode framework. When the agent method is called, the operation of the agent is realized by intercepting the method.
In general, JDK dynamic agent uses interface to realize agent, CGLIB dynamic agent uses inheritance to realize agent.

Dynamic agent is very common in Java development. It is widely used in log, monitoring and transaction. At the same time, it is also indispensable in the core components of most mainstream frameworks. It is necessary to master its key points, no matter when developing or reading the source code of other frameworks.

Personal blog: https://ytao.top
Pay attention to the official account [ytao], more original and good articles.

Topics: Java JDK Attribute jvm