Detailed explanation of agent mode and dynamic agent

proxy pattern

Proxy pattern is a structural design pattern that allows you to provide alternatives to objects or placeholders for them. The agent controls access to the original object and allows some processing before and after the request is submitted to the object.

Agent mode structure

  1. The service interface declares the functions provided by the service interface. The proxy must follow this interface to masquerade as an object
  2. Service class, which provides some practical business logic
  3. The proxy class contains a reference member variable that points to the service object. After the proxy completes its assigned tasks (such as delayed loading, logging, access control or caching), it will pass the request to the service object. Generally, the proxy will manage the whole declaration cycle of its service object.
  4. The client can interact with the service or proxy through the same interface, so you can use the proxy in some code that needs the service object.

case analysis

We have a common database access interface. A large number of clients directly access the database, which consumes a lot of system resources, and there are a lot of repeated query operations.

Accessing the database directly may be very slow

At this time, we consider adding the cache. When repeated queries are needed, we can directly get the data from the cache and return it to the client to save system overhead, and record the time spent on each client access.

In proxy mode, it is recommended to create a new proxy class with the same interface as the original service object, and then update the application to pass the proxy object to all original object clients. After receiving the client request, the proxy class will create the actual service object and delegate all work to it.

The agent disguises itself as a database object, which can do cache query operation without the client's knowledge, and record its access time or log

code implementation

Define the interface to query the database

public interface DataService {
    // Query data by ID
    String getById(Integer id);
}

Specific database query business class

public class DataServiceImpl implements DataService{

    // Analog data
    final Map<Integer,String> dataMap = new HashMap<Integer,String>(){{
        for (int i = 0; i < 10; i++) {
            put(i,"data_"+ i);
        }
    }};

    @Override
    public String getById(Integer id) {
        // Time consuming for simulating database query
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return dataMap.get(id);
    }
}

Create agent class and disguise business class

public class DataServiceProxy implements DataService{
    DataService dataService;
    // cache
    Map<Integer,String> cacheMap = new HashMap<>();
    
    public DataServiceProxy(DataService dataService) {
        this.dataService = dataService;
    }

    @Override
    public String getById(Integer id) {
        // Record the start time of the visit
        final long start = System.currentTimeMillis();
        String result = null;
        // Get priority from cache
        String cache = getCache(id);
        if (cache == null){
            result = dataService.getById(id);
            // Put in cache
            putCache(id,result);
        }else {
            result = cache;
        }
        final long end = System.currentTimeMillis();
        System.out.println("Time consuming:" + (end - start) + "ms");
        return result;
    }

    // Cache information
    private void putCache(Integer id,String value){
        cacheMap.put(id,value);
    }
    // Get cache information
    private String getCache(Integer id){
        return cacheMap.get(id);
    }
    
}

client

@Test
public void ProxyTest() {
    DataService dataService = new DataServiceImpl();
    DataServiceProxy dataServiceProxy = new DataServiceProxy(dataService);
    dataServiceProxy.getById(1);
    // Second query
    dataServiceProxy.getById(1);
    dataServiceProxy.getById(1);
}

The design method of this agent mode is generally called static agent: the source code is created by the coder or generated by a specific tool. The interface, proxy class and proxy class have been determined during compilation. Before the program runs, the bytecode file of the proxy class has been generated. If you use other proxy content, you may need to create a lot of new code to implement it.

Dynamic agent

The biggest difference from static agent is that dynamic agent class is an agent created when the program is running. For example, in the above example, the DataServiceProxy proxy class is defined by ourselves and has been compiled before the program runs. In the dynamic proxy, the proxy class is not defined in the code, but dynamically generated in the Java code according to our needs when the program runs.

In Java, we mention dynamic agent, which is generally inseparable from JDK dynamic agent and CGLIB dynamic agent.

JDK dynamic agent

Using JDK's own proxy class to complete, it is equivalent to using an interceptor (to implement interface InvocationHanlder) to match with the reflection mechanism to generate an anonymous interface to implement the proxy class, and call InvocationHanlder before calling the specific method.

We still use the DataService interface and DataServiceImpl business class to complete a dynamic proxy case.

  1. Create the interface and business class of the proxy class (already available)
  2. Create the implementation class of InvocationHanlder interface and implement the logic of the agent in the invoke method
  3. Create a Proxy object through the Proxy's static method newproxyinstance (classloader, loader, class [] interfaces, invocationhandler h).
public class JDKProxy implements InvocationHandler {
    // Proxied object
    private Object object;

    // cache
    Map<Integer,String> cacheMap = new HashMap<>();

    public JDKProxy(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Proxy only the query methods
        if (method.getName().equals("getById")){
            // parameter
            Integer id = (Integer) args[0];
            // Record the start time of the visit
            final long start = System.currentTimeMillis();
            String result = null;
            // Get priority from cache
            String cache = getCache(id);
            if (cache == null){
                // Agent execution
                result =(String) method.invoke(object,args);
                // Put in cache
                putCache(id,result);
            }else {
                result = cache;
            }
            final long end = System.currentTimeMillis();
            System.out.println("Time consuming:" + (end - start) + "ms");
            return result;
        }else {
            return method.invoke(object,args);
        }
    }

    // Cache information
    private void putCache(Integer id,String value){
        cacheMap.put(id,value);
    }
    // Get cache information
    private String getCache(Integer id){
        return cacheMap.get(id);
    }
}

InvocationHandler interface details

There is an instance of the proxy handler that invokes the proxy interface; When a proxy instance invokes a method, the method call is encoded and dispatched to the invoke method of the calling handler.

The calling handler of each dynamic proxy class must implement the InvocationHandler interface, and the instance of each proxy class is associated with the dynamic proxy class calling handler that implements the interface. When we call a method through the dynamic proxy object, the call of this method will be forwarded to the invoke method that implements the InvocationHandler interface class, See the following invoke method:

/**
* proxy:Real proxy object of proxy class proxy com sun. proxy.$ Proxy0 (in order, each generated + 1)
* method:The Method object of the real Method of an object we want to call
* args:Refers to the parameters passed by the proxy object method
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

Client

The method of the client when calling is also different from that of the static Proxy. Finally, the Proxy class $Proxy is used to call the method

@Test
public void JDKProxyTest() {
    DataService dataService = new DataServiceImpl();
    JDKProxy jdkProxy = new JDKProxy(dataService);
    // Get proxy object
    DataService dataServiceProxy = (DataService) Proxy.newProxyInstance(DataService.class.getClassLoader(), new Class[]{DataService.class}, jdkProxy);
    dataServiceProxy.getById(1);
    dataServiceProxy.getById(1);
}

The result of its operation is the same, and the agent content is completed.

Proxy class details

Proxy class is a class used to create a proxy object. It provides many methods. The most commonly used method is newProxyInstance.

public static Object newProxyInstance(ClassLoader loader, 
                                            Class<?>[] interfaces, 
                                            InvocationHandler h)

newProxyInstance is to create a proxy class object, which receives three parameters:

  • Loader: Specifies the class loader of the proxy class (we pass in the class loader of the current test class)
  • interfaces: an array of interface objects, the interface that the proxy class needs to implement (we pass in the interface implemented by the proxy class, so that the generated proxy class and the proxy class implement the same interface)
  • h: An InvocationHandler object indicates which InvocationHandler object will be associated with when the dynamic proxy object calls the method to handle the method call. Here we pass in our own handler

CGLIB dynamic proxy

Using asm open source package, load the class file of proxy object class, and modify its bytecode to generate subclasses for processing.

  1. Import cglib XXX Jar package, which contains asm and cglib
  2. Create the implementation class of MethodInterceptor interface and implement the logic of agent in intercept method
  3. Write the getCglibProxy method (custom) to return the proxy class object

Pom import cglb

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

Override MethodInterceptor

public class CglibProxy implements MethodInterceptor {
    // The proxy Object is convenient for general use and can be written as Object
    private Object object;

    // cache
    Map<Integer,String> cacheMap = new HashMap<>();

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // Proxy only the query methods
        if (method.getName().equals("getById")) {
            // parameter
            Integer id = (Integer) args[0];
            // Record the start time of the visit
            final long start = System.currentTimeMillis();
            String result = null;
            // Get priority from cache
            String cache = getCache(id);
            if (cache == null) {
                result = (String)method.invoke(object,args);
                // Put in cache
                putCache(id, result);
            } else {
                result = cache;
            }
            final long end = System.currentTimeMillis();
            System.out.println("Time consuming:" + (end - start) + "ms");
            return result;
        } else {
            return method.invoke(object, args);
        }
    }

    // Get the proxy object, which adopts the writing method of paradigm, more intuitively passes in the proxy class, and then returns the proxy object
    public <T> T getCglibProxy(T t){
        this.object = t;//Assign a value to the target object target
        Enhancer enhancer = new Enhancer();
        //Set the parent class. Because Cglib generates a subclass for the specified class, you need to specify the parent class
        enhancer.setSuperclass(object.getClass());
        //Set callback
        enhancer.setCallback(this);
        //Create and return proxy objects
        Object result = enhancer.create();
        return (T) result;
    }

    // Cache information
    private void putCache(Integer id,String value){
        cacheMap.put(id,value);
    }
    // Get cache information
    private String getCache(Integer id){
        return cacheMap.get(id);
    }
}

Client

@Test
public void CGLBProxyTest(){
    // The proxy class does not need interface declaration here
    DataService dataService = new DataServiceImpl();
    CglibProxy cglibProxy = new CglibProxy();
    // Get proxy object
    DataService proxy = cglibProxy.getCglibProxy(dataService);
    proxy.getById(1);
    proxy.getById(1);
}

It can be found that the writing methods of the two dynamic agents are basically the same. The basic idea is to generate agent classes, intercept, reflect, obtain real agent class methods and execute. So what's the difference between the two ways?

Differences between JDK agent and CGLIB agent

  1. JDK dynamic agent can only generate agents for classes that implement interfaces, not for classes. It is implemented by Java reflection technology, and the process of generating classes is relatively efficient.
  2. CGLIB is a proxy for class implementation. It mainly generates a subclass of a specified class and covers its methods. It is implemented using asm bytecode framework. The related execution process is more efficient. The process of generating a class can be compensated by cache. Because it is inheritance, it is better not to declare this class or method as final
  3. JDK agent does not need the support of a third-party library. It can be used only in the JDK environment
  4. CGLIB must rely on CGLIB's class library, but it needs classes to implement any interface proxy. What it needs is to generate a subclass from the specified class and override the methods in it. It is an inherited proxy, but it is recommended to use JDK in the environment of interface programming;

Keywords: Java Design Pattern

Added by nivek9991 on Tue, 08 Mar 2022 08:03:53 +0200