Cglib Dynamic Agent Implementation Principle. md

Keywords: Java JDK Spring Hibernate

1. Introduction to Cglib Library

CGLIB is a powerful and high performance code generation library. It is widely used in proxy-based AOP frameworks (such as Spring AOP and dynaop) to provide method interception. Hibernate, as the most popular ORM tool, also uses CGLIB libraries to proxy single-ended associations (except for collection lazy loading, which uses another mechanism). EasyMock and jMock are popular Java test libraries. They provide Mock objects to support testing. Both use CGLIB to proxy classes without interfaces.

Within the implementation, the CGLIB library uses ASM, a lightweight but high-performance bytecode operation framework, to transform bytecode and generate new classes. In addition to CGLIB, scripting languages like Groovy and Bean Shell also use ASM to generate Java bytecodes. ASM uses a mechanism similar to SAX analyzer to achieve high performance. We do not recommend using ASM directly, because this requires a good understanding of JVM, including class file formats and instruction sets.

The above figure shows the framework of the CGLIB library and the relationship between languages. Another reminder is that frameworks like Spring AOP and Hibernate often use both CGLIB and JDK dynamic proxies to meet their respective needs. Hibernate uses JDK dynamic proxy to implement a transaction management adapter for Web Shere application services; Spring AOP uses JDK dynamic proxy by default to proxy interfaces unless you force CGLIB to be used.

2. Cglib API

The CGLIB library has a small amount of code, but it is difficult to learn due to lack of documentation. The organization of the CGLIB library is as follows:

net.sf.cglib.beans: JavaBean-related tool classes
 net.sf.cglib.core: The underlying bytecode operation class; most are ASP-related
 net.sf.cglib.proxy: Proxy creates classes, method intercepts classes
 net.sf.cglib.reflect: Faster reflection class, C# style proxy class
 net.sf.cglib.transform: class file conversion classes at compile and run time
 net.sf.cglib.util: Collection Sorting Tool Class

For creating dynamic proxies, in most cases you only need to use part of the API of the proxy package.

As mentioned above, the CGLIB library is based on the upper application of ASM. CGLIB is very useful for classes whose proxies do not implement interfaces. Essentially, for classes that need to be proxied, it just dynamically generates a subclass to override non-final methods while binding hooks to call back custom interceptors. It's worth mentioning that it's faster than JDK dynamic proxy

The API association diagram for proxy classes is often used in CGLIB libraries as shown above. net.sf.cglib.proxy.Callback is just an interface for markup, which is inherited by all callbacks used by net.sf.cglib.proxy.Enhancer.

net.sf.cglib.proxy.MethodInterceptor is the most commonly used callback type, which is often used to intercept method callbacks in proxy-based AOP implementations. This interface has only one method:

public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;

If net.sf.cglib.proxy.MethodInterceptor is set to a method callback, when a proxy method is called, it calls the MethodInterceptor.intercept method first, and then the method of the proxy object (as shown in the figure below). The first parameter of the MethodInterceptor. intercept method is the proxy object, and the second and third parameters are the parameters of the intercepted method and method, respectively. If you want to call the original method of the proxy object, you can use the java.lang.reflect.Method object to reflect the call, or the net.sf.cglib.proxy.MethodProxy object. We usually use net. sf. cglib. proxy. MethodProxy because it's faster. In intercept methods, custom code can be injected before or after the original method call.

net.sf.cglib.proxy.MethodInterceptor meets all the proxy requirements, but it may not be very convenient for some specific scenarios. For ease of use and high performance, CGLIB provides other special callback types. For example,

net.sf.cglib.proxy.FixedValue: It is very useful and high performance in a particular scenario to force a particular method to return a fixed value.
net.sf.cglib.proxy.NoOp: It passes directly to the method implementation of the parent class.
net.sf.cglib.proxy.LazyLoader: This is very useful in situations where the proxy object needs to be lazily loaded. If the proxy object is loaded, it will be reused in future proxy calls.
net.sf.cglib.proxy.Dispatcher: Similar to net.sf.cglib.proxy.LazyLoader, but each time the proxy method is called, the loadObject method is called to load the proxy object.
net.sf.cglib.proxy.ProxyRefDispatcher: The same as Dispatcher, but its loadObject method supports incoming proxy objects.
We usually use the same callback for all methods of the proxy class (as shown in Figure 3 above), but we can also use net.sf.cglib.proxy.CallbackFilter to use different callbacks for different methods. This fine-grained control is not provided by JDK dynamic proxy. The invoke method of java.lang.reflect.InvocationHandler in JDK can only be applied to all methods of the proxy object.

In addition to proxy classes, CGLIB can also proxy interfaces through java.lang.reflect.Proxy insertion substitution to support agents before JDK 1.3, but because this substitution agent is rarely used, the relevant proxy API is omitted here.

Now let's see how to use CGLIB to create proxies.

3. Simple Agent

The core of CGLIB proxy is the net.sf.cglib.proxy.Enhancer class. For creating a CGLIB proxy, you must have at least one proxy class. Now let's use the built-in NoOp callback:

/**
 * Create a proxy using NoOp callback. The target class
 * must have a default zero-argument constructor.
 *
 * @param targetClass the super class of the proxy
 * @return a new proxy for a target class instance
 */
public Object createProxy(Class targetClass) {
     Enhancer enhancer = new Enhancer();
     enhancer.setSuperclass(targetClass);
     enhancer.setCallback(NoOp.INSTANCE);
     return enhancer.create();
}

The return value of this method is a proxy for the target class object. In the above example, net.sf.cglib.proxy.Enhancer configures a single net.sf.cglib.proxy.Callback. As you can see, it's easy to create a simple proxy using CGLIB. In addition to creating a new net. sf. cglib. proxy. Enhancer object, you can also create proxies directly using static auxiliary methods in the net. sf. cglib. proxy. Enhancer class. But we recommend using the method in the example, because you can configure the net. sf. cglib. proxy. Enhancer object to control the generated agent more finely.

Notably, we pass in the target class as the parent of the proxy. Unlike JDK dynamic proxies, we cannot use target objects to create proxies. The target object can only be created by CGLIB. In the example, the default parametric constructor is used to create the target object. If you want CGLIB to create an instance with parameters, you should use net.sf.cglib.proxy.Enhancer.create(Class[], Object []). The first parameter of the method specifies the parameter type, and the second parameter specifies the parameter value. Atomic types in parameters require wrapping classes

4. Use Method Interceptor

We can replace the net.sf.cglib.proxy.NoOp callback with a custom net. sf. cglib. proxy. Method Interceptor to get a stronger proxy. All method calls of the proxy are assigned to the intercept method of net.sf.cglib.proxy.MethodInterceptor. The intercept method then calls the underlying object.

Suppose you want to check the authorization of method calls to the target object, and if the authorization fails, throw a runtime exception, AuthorizationException. The interface Authorization.java is as follows:

package com.lizjason.cglibproxy;

import java.lang.reflect.Method;

/**
 *  A simple authorization service for illustration purpose.
 *
 * @author Jason Zhicheng Li (jason@lizjason.com)
 */
public interface AuthorizationService {
    /**
     * Authorization check for a method call. An AuthorizationException
     * will be thrown if the check fails.
     */
    void authorize(Method method);
}

The implementation of interface net.sf.cglib.proxy.MethodInterceptor is as follows:

package com.lizjason.cglibproxy.impl;

import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import com.lizjason.cglibproxy.AuthorizationService;

/**
 * A simple MethodInterceptor implementation to
 * apply authorization checks for proxy method calls.
 *
 * @author Jason Zhicheng Li (jason@lizjason.com)
 *
 */
public class AuthorizationInterceptor implements MethodInterceptor {
    private AuthorizationService authorizationService;

    /**
     * Create a AuthorizationInterceptor with the given
     * AuthorizationService
     */
    public AuthorizationInterceptor (AuthorizationService authorizationService) {
        this.authorizationService = authorizationService;
    }

    /**
     * Intercept the proxy method invocations to inject authorization check.
     * The original method is invoked through MethodProxy.
     * @param object the proxy object
     * @param method intercepted Method
     * @param args arguments of the method
     * @param proxy the proxy used to invoke the original method
     * @throws Throwable any exception may be thrown; if so, super method will not be invoked
     * @return any value compatible with the signature of the proxied method.
     */
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy ) throws Throwable {
        if (authorizationService != null) {
            //may throw an AuthorizationException if authorization failed
            authorizationService.authorize(method);
        }
        return methodProxy.invokeSuper(object, args);
    }
}

In the intercept method, the authorization is checked first, and if the authorization passes, the intercept method calls the method of the target object. For performance reasons, we use CGLIB's net.sf.cglib.proxy.MethodProxy object instead of the general java.lang.reflect.Method reflection object to invoke the original method

5. Use CallbackFilter

net.sf.cglib.proxy.CallbackFilter allows you to set callbacks at the method level. Suppose you have a PersistenceService Impl class, which has two methods: save and load. The save method needs authorization checks, while the load method does not.

package com.lizjason.cglibproxy.impl;

import com.lizjason.cglibproxy.PersistenceService;

/**
 * A simple implementation of PersistenceService interface
 *
 * @author Jason Zhicheng Li (jason@lizjason.com)
 */
public class PersistenceServiceImpl implements PersistenceService {

    public void save(long id, String data) {
        System.out.println(data + " has been saved successfully.");
    }

    public String load(long id) {
        return "Jason Zhicheng Li";
    }
}

The PersistenceService Impl class implements the PersistenceService interface, but this is not necessary

The implementation of net.sf.cglib.proxy.CallbackFilter of Persistence Service Impl is as follows:

package com.lizjason.cglibproxy.impl;

import java.lang.reflect.Method;
import net.sf.cglib.proxy.CallbackFilter;

/**
 * An implementation of CallbackFilter for PersistenceServiceImpl
 *
 * @author Jason Zhicheng Li (jason@lizjason.com)
 */
public class PersistenceServiceCallbackFilter implements CallbackFilter {

    //callback index for save method
    private static final int SAVE = 0;

    //callback index for load method
    private static final int LOAD = 1;

    /**
     * Specify which callback to use for the method being invoked.
     * @method the method being invoked.
     * @return the callback index in the callback array for this method
     */
    public int accept(Method method) {
        String name = method.getName();
        if ("save".equals(name)) {
            return SAVE;
        }
        // for other methods, including the load method, use the
        // second callback
        return LOAD;
    }
}

The accept method maps the proxy method to the callback. The method return value is a subscript in an array of callback objects. The following is a proxy creation implementation of Persistence Service Impl:

...
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersistenceServiceImpl.class);

CallbackFilter callbackFilter = new PersistenceServiceCallbackFilter();
enhancer.setCallbackFilter(callbackFilter);

AuthorizationService authorizationService = ...
Callback saveCallback = new AuthorizationInterceptor(authorizationService);
Callback loadCallback = NoOp.INSTANCE;
Callback[] callbacks = new Callback[]{saveCallback, loadCallback };
enhancer.setCallbacks(callbacks);
...
return (PersistenceServiceImpl)enhancer.create();

In the example, Authorization Interceptor is applied to save method and NoOp.INSTANCE is applied to load method. You can specify the interfaces the agent needs to implement through net.sf.cglib.proxy.Enhancer.setInterfaces(Class []), but this is not necessary.

For net.sf.cglib.proxy.Enhancer, in addition to setting up an array of callback objects, you can also use net.sf.cglib.proxy.Enhancer.setCallbackTypes(Class []) to set up an array of callback types. This method is very useful if you do not have an actual callback object during agent creation. Like callback objects, you also need to use net.sf.cglib.proxy.CallbackFilter to specify callback type subscripts for each interception method.

6. summary

CGLIB is a powerful and high performance code generation library. As complementary to JDK dynamic proxy, it provides proxy solutions for classes that do not implement interfaces. At the bottom, it uses the ASM bytecode manipulation framework. Essentially, CGLIB proxies by generating subclasses to override non-final methods. It is faster than the JDK dynamic proxy method using Java reflection. CGLIB cannot proxy a final class or final method. Generally speaking, you can use JDK dynamic proxy method to create proxy. CGLIB is a good choice for situations without interfaces or performance factors.

Posted by dannau on Wed, 20 Mar 2019 11:21:28 -0700