CGLIB dynamic agent mechanism has been written in all aspects

CGLIB library introduction

The agent provides an extensible mechanism to control the access of the object. In fact, it adds a layer of encapsulation when the object is accessed. JDK has provided a dynamic proxy since version 1.3. It is very simple to use, but it has an obvious disadvantage: it requires the target object to implement one or more interfaces. What if you want to proxy a class without an interface? You can use the 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 library to proxy single end Association (except for lazy loading of collection, which uses another mechanism). EasyMock and jMock are popular Java test libraries. They provide Mock objects to support testing. CGLIB is used to proxy classes without interfaces.

In the implementation, the CGLIB library uses ASM, a lightweight but high-performance bytecode operation framework, to convert bytecode and generate new classes. In addition to CGLIB, scripting languages like Groovy and bean shell also use ASM to generate Java bytecode. ASM uses a mechanism similar to SAX analyzer to achieve high performance. We do not recommend using ASM directly because it requires a good understanding of the JVM, including the class file format and instruction set.

The figure above shows the CGLIB library related framework and the relationship between languages. In addition, frameworks like Spring AOP and Hibernate often use both CGLIB and JDK dynamic proxies to meet their needs. Hibernate uses JDK dynamic proxy to implement a transaction management adapter for webshare application services; Spring AOP uses JDK dynamic proxy to proxy the interface by default, unless you force CGLIB.

CGLIB API

CGLIB library has a small amount of code, but it is difficult to learn due to lack of documents. The CGLIB Library of version 2.1.2 is organized as follows:

  • net.sf.cglib.core: the underlying bytecode operation class; mostly ASP related.
  • net.sf.cglib.transform: class file conversion class in compilation and runtime.
  • net.sf.cglib.proxy: proxy creation class and method interception class.
  • net.sf.cglib.reflect: faster reflection class, C ා style proxy class.
  • net.sf.cglib.util: Collection sorting tool class
  • net.sf.cglib.beans: JavaBean related tool classes

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

As mentioned above, CGLIB library is an upper application based on ASM. CGLIB is very useful for classes where the agent does not implement an interface. In essence, for classes that need to be proxied, it just dynamically generates a subclass to cover non final methods, and binds hooks to call back custom interceptors. It's worth saying that it's faster than the JDK dynamic proxy.

The API association diagram that is often used to proxy classes in the CGLIB library is shown above. net.sf.cglib.proxy.Callback It's just an interface for tagging, net.sf.cglib.proxy.Enhancer All callbacks used inherit this interface.

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

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

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

net.sf.cglib.proxy.MethodInterceptor All the agent requirements are met, but it may not be 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 specific scenarios to force a specific 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 : it is very useful when the proxy object needs to be loaded lazily. If the proxy object is loaded, it will be reused in the later proxy calls.
  • net.sf.cglib.proxy.Dispatcher : and net.sf.cglib.proxy.LazyLoader Almost, but every time the proxy method is called, the loadObject method is called to load the proxied object.
  • net.sf.cglib.proxy.ProxyRefDispatcher : same as Dispatcher, but its loadObject method supports incoming proxy objects.

We usually use the same callback for all methods of the proxied class (as shown in Figure 3 above), but we can also use the net.sf.cglib.proxy.CallbackFilter To use different callbacks for different methods. This kind of fine-grained control is not provided by the JDK dynamic agent java.lang.reflect The invoke method of the. Invocationhandler can only be applied to all methods of the proxied object.

In addition to proxy classes, CGLIB can also use the java.lang.reflect.Proxy inserts a replacement to proxy the interface to support the proxy before JDK 1.3, but because this replacement proxy is rarely used, the relevant proxy API is omitted here.

Now let's see how to use CGLIB to create an agent.

Simple agent

The core of CGLIB agent is net.sf.cglib.proxy.Enhancer Class. To create 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 of the target class object. In the example above, net.sf.cglib.proxy.Enhancer Single configuration 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 use it directly net.sf.cglib.proxy.Enhancer Class to create a proxy. But we recommend using the method in the example, because you can configure net.sf.cglib.proxy.Enhancer Object to control the resulting proxy more precisely.

It is worth noting that we passed in the target class as the parent of the proxy. Unlike the JDK dynamic proxy, we cannot use the target object to create the proxy. The target object can only be created by CGLIB. In the example, the default nonparametric construction method 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 indicates the parameter type, and the second parameter indicates the parameter value. The atomic type in the parameter requires a wrapper class.

Using MethodInterceptor

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

Suppose you want to check the authorization of the method call of the target object. If the authorization fails, a runtime exception authorization exception will be thrown. Interface Authorization.java 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)
 */
10public interface AuthorizationService {
    /**
     * Authorization check for a method call. An AuthorizationException
     * will be thrown if the check fails.
     */
    void authorize(Method method);
}

Interface net.sf.cglib.proxy.MethodInterceptor The implementation 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, first check the authorization. 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, not general java.lang.reflect.Method reflection object to call the original method.

Using CallbackFilter

net.sf.cglib.proxy.CallbackFilter Allows you to set callbacks at the method level. Suppose you have a PersistenceServiceImpl class with two methods: save and load. The save method requires authorization checking, 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 PersistenceServiceImpl class implements the PersistenceService interface, but this is not required. PersistenceServiceImpl's net.sf.cglib.proxy.CallbackFilter The implementation 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. Method return value is a subscript in an array of callback objects. The following is the proxy creation implementation of PersistenceServiceImpl:

...
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, the AuthorizationInterceptor is applied to the save method, NoOp.INSTANCE Apply to the load method. You can go through net.sf.cglib.proxy.Enhancer.setInterfaces(Class []) indicates the interface that the agent needs to implement, but this is not required.

about net.sf.cglib.proxy.Enhancer In addition to setting up an array of callback objects, you can also use the net.sf.cglib.proxy.Enhancer.setCallbackTypes(Class []) sets an array of callback types. This is useful if you don't have an actual callback object during proxy creation. Like callback objects, you need to use the net.sf.cglib.proxy.CallbackFilter To indicate the callback type subscript for each intercepting method. You can focus on the official account: download the complete sample code in the zero world.

summary

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

If the article is wrong, welcome the review guide, and you can also pay attention to the official account: the program zero world gets more source data.

Tags: Java JDK Spring Hibernate

Posted on Thu, 04 Jun 2020 04:48:56 -0400 by Tobeon