[design mode self study room] detailed explanation of agent mode

Preface

As the name implies, this series of articles take you to review the common design patterns. The main contents are as follows:

  • The introduction of this mode includes:
    • Introduction, intention (in vernacular)
    • Class diagram, sequence diagram (theoretical specification)
  • Code example of the pattern: familiar with the code of the pattern
  • Advantages and disadvantages of the model: the model is not a golden oil and can not be abused
  • The application case of this pattern: understand which important source code it is used in

This series will be updated gradually in my blog and public address (at the bottom of the blog), and I hope all of you will be able to pay attention to my personal public address: the back-end technology talks, and I won't miss the wonderful articles.

Review of the series

Structural - Proxy Pattern

Introduction

Generally speaking, agency mode is the common intermediary in our life. In some cases, a customer doesn't want or can't directly refer to an object. In this case, indirect reference can be realized through a third party called "agent".

Why use agent mode

  • Mediation isolation: in some cases, a client class does not want or can not directly reference a delegate object, while a proxy class object can mediate between the client class and the delegate object, which is characterized by that the proxy class and the delegate class implement the same interface.
  • Open close principle, add function: the real business function is implemented by the delegation class, but some public services can be added before and after the execution of business function. For example, if we want to add caching and logging functions to the project, we can use the proxy class to complete it, without opening the encapsulated delegate class.

Definition

The proxy mode provides a proxy object for an object, and the proxy object controls the reference to the original object.

Common agents are divided into static agents and dynamic agents:

1. Static agent

The bytecode file of the agent class already exists before the program runs. The relationship between the agent class and the real subject role is determined before the program runs.

Is created by a programmer or a specific tool to automatically generate source code before compiling it. The agent class. Class file has been created before the programmer can run it.

2. Dynamic agent

Why can classes be generated dynamically?

This involves the class loading mechanism of Java virtual machine

The loading process of Java virtual machine class is mainly divided into five stages: loading, verification, preparation, parsing and initialization. Three things need to be done in the loading phase:

  1. Get the binary byte stream that defines a class by its fully qualified name
  2. Convert the static storage structure represented by this byte stream to the runtime data structure of the method area
  3. A java.lang.Class object representing this class is generated in memory as the data access entry of this class in the method area

Because the virtual machine specification does not specifically require these three points, the actual implementation is very flexible. As for point 1, there are many ways to obtain the class binary byte stream (class byte code):

  1. Obtained from ZIP package, which is the basis of JAR, EAR, WAR and other formats
  2. Get from the network, typical application is Applet
  3. The most commonly used scenario in runtime computing generation is dynamic Proxy technology. In the java.lang.reflect.Proxy class, ProxyGenerator.generateProxyClass is used to generate binary byte stream of Proxy class in the form of * $Proxy for a specific interface
  4. Generated by other files, typical application is JSP, that is, JSP files generate corresponding Class classes
  5. Get from database, etc

Therefore, dynamic proxy is to find a way to calculate the bytecode of the proxy class according to the interface or target object, and then load it into the JVM for use.

More Java class loading mechanisms can be found in:

Quick Review Manual of knowledge points of Java virtual machine

There are two typical ways to realize dynamic agent: JDK dynamic agent and CGLib dynamic agent

  • By implementing the interface - > JDK dynamic agent
  • By inheriting the class - > cglib dynamic proxy

2.1 JDK reflection mechanism (interface agent)

  • It is created dynamically by reflection mechanism when the program is running.
  • Generate a proxy object for the interface to be intercepted to implement the interface method interception function.

2.2 CGLIB agent

  • Its principle is to create a subclass for a class by bytecode technology, and intercept all the calls of the parent class methods by method interception technology in the subclass, and weave the crosscutting logic along the trend.
  • However, because inheritance is used, the final decorated class cannot be proxied.
  • Both JDK dynamic agent and CGLib dynamic agent are the foundation of Spring AOP.

Class diagram

If you don't understand the UML class diagram, you can browse it roughly first. If you want to know more about it, you can continue to Google and learn more about it

[the external link picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-pswzmwwe-1579686913106) (https://user-gold-cdn.xitu.io/2020/1/22/16fccab428d8c0d6? W = 709 & H = 347 & F = PNG & S = 100832))

The agent mode includes the following roles:

  • Subject (abstract subject role): defines the public external method of agent class and real subject, and also the method of agent class acting real subject;
  • RealSubject: a class that truly implements business logic;
  • Proxy (proxy theme role): used to proxy and encapsulate real themes;

Sequence diagram

Code implementation and usage scenarios

The proxy pattern has been widely used, the most commonly used is the CGlib proxy we use in Spring, so we will integrate the code examples and the use scenarios again.

It is mainly divided into four code small Demo, which are:

  • Static agent code example
  • JDK dynamic agent code example
  • Example of ASM bytecode insertion technology code used by CGLIB bottom layer
  • CGLIB dynamic proxy code example

1. Static agent example code

Write an interface UserService and its implementation class UserServiceImpl

public interface UserService {
    public void select();   
    public void update();
}

public class UserServiceImpl implements UserService {  
    public void select() {  
        System.out.println("query selectById");
    }
    public void update() {
        System.out.println("To update update");
    }
}

We will enhance the UserServiceImpl with a static agent, and record some logs (recording the start and end time points) before calling select and update. Write a proxy class UserServiceProxy. The proxy class needs to implement UserService

public class UserServiceProxy implements UserService {
    private UserService target; // Represented object

    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    public void select() {
        before();
        target.select();    // Here we actually call the method of the real theme role
        after();
    }
    public void update() {
        before();
        target.update();    // Here we actually call the method of the real theme role
        after();
    }

    private void before() {     // Execute before executing method
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {      // Execute after method execution
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

Client test

public class Client1 {
    public static void main(String[] args) {
        UserService userServiceImpl = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(userServiceImpl);

        proxy.select();
        proxy.update();
    }
}

Through the static agent, we achieve the purpose of function enhancement, and do not invade the original code, which is an advantage of the static agent.

2. JDK dynamic agent

JDK dynamic proxy mainly involves two classes: java.lang.reflect.Proxy and java.lang.reflect.InvocationHandler

Write a calling logic processor LogHandler class to provide log enhancement and implement the InvocationHandler interface; maintain a target object in LogHandler, which is the object being proxied (real subject role); write the logic processing of method calls in the invoke method

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class LogHandler implements InvocationHandler {
    Object target;  // Represented object, actual method executor

    public LogHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);  // Call target's method method
        after();
        return result;  // Returns the execution result of the method
    }
    // Execute before invoking the invoke method
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    // Execute after invoking the invoke method
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

To write the client and obtain the dynamically generated Proxy objects, you must use the newProxyInstance method of the Proxy class. See the code and comments for the specific steps

import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client2 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        // Set variable to save dynamic proxy class. The default name is named in $Proxy0 format
        // System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 1. Create the proxy object and the implementation class of the UserService interface
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        // 2. Get the corresponding ClassLoader
        ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
        // 3. Obtain the Class of all interfaces. The UserServiceImpl here only implements one interface, UserService,
        Class[] interfaces = userServiceImpl.getClass().getInterfaces();
        // 4. Create a call request handler that will be passed to the proxy class to handle method calls on all proxy objects
        //     What is created here is a custom log processor, which needs to pass in the actual execution object userServiceImpl
        InvocationHandler logHandler = new LogHandler(userServiceImpl);
        /*
           5.According to the information provided above, during the process of creating a proxy object,
               a.JDK The bytecode equivalent to the. class file is dynamically created in memory based on the parameter information passed in
               b.Then convert it to the corresponding class according to the corresponding bytecode,
               c.Then call newInstance() to create the proxy instance.
         */
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
        // Method to call the agent
        proxy.select();
        proxy.update();

        // Save the proxy class generated by JDK dynamic proxy. The class name is saved as UserServiceProxy
        // ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");
    }
}

Result:

log start time [Thu Dec 20 16:55:19 CST 2018] 
//Query selectById
log end time [Thu Dec 20 16:55:19 CST 2018] 
log start time [Thu Dec 20 16:55:19 CST 2018] 
//update
log end time [Thu Dec 20 16:55:19 CST 2018] 

The sample codes in the above 1 and 2 are from, and the detailed analysis is given in this paper:

http://laijianfeng.org/2018/12/Java-%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E8%AF%A6%E8%A7%A3/

3. CGLib dynamic agent

CGLIB proxy generates proxy classes by inheritance.

Bytecode modification example code

First of all, let's understand how bytecode modification technology is implemented through code

We use the ASM bytecode operation class library (ASM is used at the bottom of cglib) to give an example code. You can also run it locally.

ASM can generate binary class files directly, which can be used to generate classes dynamically or enhance the function of existing classes.

After reading information from class files, ASM can change class behavior, analyze class information, and even generate new classes according to user requirements.

Compared with other similar tools such as BCEL, SERP, Javassist and cglib, the biggest advantage of ASM lies in its higher performance. Its jar package is only 30K. Both Hibernate and Spring use cglib agent, while the bottom layer of cglib uses ASM. It can be seen that ASM is widely used in various open source frameworks.

Base class: the modified class that outputs a process every 3 seconds to simulate the request being processed.

package asm;

import java.lang.management.ManagementFactory;

public class Base {
    public static void main(String[] args) {
        String name = ManagementFactory.getRuntimeMXBean().getName();
        String s = name.split("@")[0];
        //Print current Pid
        System.out.println("pid:"+s);
        while (true) {
            try {
                Thread.sleep(3000L);
            } catch (Exception e) {
                break;
            }
            process();
        }
    }

    public static void process() {
        System.out.println("process");
    }
}

Class that performs bytecode modification and conversion: in this class, we implement the method of outputting start and end statements before and after the modified class

public class TestTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println("Transforming " + className);
        try {
            ClassPool cp = ClassPool.getDefault();
            CtClass cc = cp.get("asm.Base");
            CtMethod m = cc.getDeclaredMethod("process");
            m.insertBefore("{ System.out.println(\"start\"); }");
            m.insertAfter("{ System.out.println(\"end\"); }");
            return cc.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

Then we generate the Jar package modified by bytecode

public class TestAgent {
    public static void agentmain(String args, Instrumentation inst) {
        inst.addTransformer(new TestTransformer(), true);
        try {
            inst.retransformClasses(TransformTarget.class);
            System.out.println("Agent Load Done.");
        } catch (Exception e) {
            System.out.println("agent load failed!");
        }
    }
}

We write the generated agent.jar to the executing Base process through the JVM Tool. It can be seen that the class that only outputs procss in 3 seconds has changed to the class that outputs start and end from before and after, and the class has been successfully modified.

I put the Demo code modified by the above bytecode in my Github warehouse:

https://github.com/qqxx6661/Java_Practise/tree/master/ASMDemo

CGLib dynamic proxy code example

maven introduces CGLIB package and writes a UserDao class. It has no interface but two methods, select() and update()

public class UserDao {
    public void select() {
        System.out.println("UserDao query selectById");
    }
    public void update() {
        System.out.println("UserDao To update update");
    }
}

Write a LogInterceptor that inherits MethodInterceptor and is used for intercepting callback of method

import java.lang.reflect.Method;
import java.util.Date;

public class LogInterceptor implements MethodInterceptor {
    /**
     * @param object Represents the object to be enhanced
     * @param method Means to intercept
     * @param objects Array represents the parameter list. The basic data type needs to be passed in its packing type, such as int -- > integer, long long, double -- > double
     * @param methodProxy Represents a proxy for a method, and invokeseuper represents a call to a method of the object being proxied
     * @return results of enforcement
     * @throws Throwable
     */
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(object, objects);   // Note that invokeseuper is called instead of invoking. Otherwise, the loop is dead. Methodproxy.invokeseuper executes the methods of the original class, and method.invoke executes the methods of the subclass
        after();
        return result;
    }
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

Test class

import net.sf.cglib.proxy.Enhancer;

public class CglibTest {
    public static void main(String[] args) {
        DaoProxy daoProxy = new DaoProxy(); 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dao.class);  // Set super class, cglib is implemented by inheritance
        enhancer.setCallback(daoProxy);

        Dao dao = (Dao)enhancer.create();   // Create proxy class
        dao.update();
        dao.select();
    }
}

The result is the same as above.

Advantages and disadvantages

Advantages and disadvantages of static agent

  • Advantages: it can expand the function of the target object in accordance with the opening and closing principle.
  • Disadvantages: when you need to proxy multiple classes, there are two ways for proxy objects to implement the same interface with the target object:
    • Only one proxy class is maintained, and multiple interfaces are implemented by this proxy class. However, the proxy class is too large
    • Create multiple proxy classes, each target object corresponds to a proxy class, but this will generate too many proxy classes

Advantages and disadvantages of JDK dynamic agent

  • Advantage: compared with static agent, dynamic agent greatly reduces our development tasks, reduces the dependence on business interface and reduces the coupling degree.
  • Disadvantage: can only proxy the interface

Advantages and disadvantages of CGLIB dynamic agent

CGLIB creates dynamic proxy objects with higher performance than JDK, but CGLIB takes much more time to create proxy objects than JDK.

  • Therefore, for single instance objects, CGLIB is suitable, because there is no need to create objects frequently. On the contrary, JDK is more suitable.
  • At the same time, because CGLib uses the method of creating subclass dynamically, it can't proxy the final decorated method.

Reference resources

  • https://www.cnblogs.com/daniels/p/8242592.html
  • http://laijianfeng.org/2018/12/Java-%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E8%AF%A6%E8%A7%A3/
  • https://www.cnblogs.com/jie-y/p/10732347.html

supplement

The difference between decoration mode and agency mode

The decorator pattern focuses on adding methods dynamically on an object, while the proxy pattern focuses on controlling access to the object.

  • When using the proxy pattern, we often create an instance of an object in a proxy class.
  • When we use the decorator pattern, we usually pass the original object as a parameter to the decorator's constructor.

Pay attention to me

I am a back-end development engineer.

Focus on back-end development, data security, crawler, Internet of things, edge computing and other directions, welcome to exchange.

I can be found on all platforms

Main content of original blog

  • Java interview Knowledge Review Manual
  • Design mode / data structure study room
  • Analysis of Leetcode / Sword finger offer algorithm
  • SpringBoot/SpringCloud rookie introduction series
  • Reptile related technical articles
  • Back end development related technical articles
  • Anecdote / good book sharing / personal interest

Personal public number: backend Technology

If the article is helpful to you, you may as well collect, coin, forward, in the~

263 original articles published, 138 praised, 380000 visitors+
His message board follow

Keywords: Java JDK Spring github

Added by phpORcaffine on Wed, 22 Jan 2020 12:37:12 +0200