A detailed explanation of the agent model of the architect's mental skill

1, Application scenario of agent mode

In our life, we often see such scenes, such as: rental and sales agency, marriage agency, broker, express delivery, etc., which are the real life embodiment of agent mode. Proxy Pattern refers to providing a proxy for other objects to control access to this object. The proxy object plays an intermediary role between the client and the target object. There are two main purposes to use the proxy mode: one is to protect the target object, the other is to enhance the target object.

Class diagram structure of agent mode:

Subject is the top-level design interface, RealSubject is the real object, Proxy is the Proxy object, the Proxy object holds the reference of the real object, the Client client calls the method of the Proxy object, and also calls the method of the real object, adding some processing before and after the Proxy object. When we think of agent pattern, we will understand it as code enhancement, which is to add some logic before and after the original code logic, so that the caller is not aware. The agent mode is divided into static agent and dynamic agent.

2, Classification of agent patterns

2.1 static agent

Let's take a direct example to illustrate the static agency. When young men and women reach the marriageable age, if there is no object, the relatives and friends around are always making a fuss about introducing an object to someone. The process of introducing an object to date is a kind of agency for all of us. Look at the code implementation:

Top level interface design Person class:

public interface Person {

    /**
     * Looking for mate
     */
    void lookForMate();
}

The daughter requires to find an object to implement the Person interface:

public class Daughter implements Person {
    @Override
    public void lookForMate() {
        System.out.println("Daughter request: tall and handsome and rich!");
    }
}

The Mother should help her daughter to date and realize the Mother class:

public class Mother {

    private Daughter daughter;
    //How to expand
    public Mother(Daughter daughter) {
        this.daughter = daughter;
    }

    //The reference of the target object is obtained by daughter and can be called
    public void lookForMate() {
        System.out.println("Mother looks for daughter");
        daughter.lookForMate();
        System.out.println("Both sides agree to communicate and establish relations");
    }
    
}

Test content:

public static void main(String[] args) {
    //I can only help my daughter find a mate, not my cousin or stranger
    Mother mother = new Mother(new Daughter());
    mother.lookForMate();
}

Operation result:

The above example is an example in our life. We implemented the agent pattern in our life with code. Let's take another example of a specific business scenario. We often divide the database into different databases and tables, and then use Java code to operate. We need to configure multiple data sources, and dynamically switch the data sources by setting the data source path. Create order entity class:

/**
 * Order entity class
 */
public class Order {

    private String id;

    private Object orderInfo;

    private Long createTime;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Object getOrderInfo() {
        return orderInfo;
    }

    public void setOrderInfo(Object orderInfo) {
        this.orderInfo = orderInfo;
    }

    public Long getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Long createTime) {
        this.createTime = createTime;
    }
}

Create OrderDao persistence layer operation class:

public class OrderDao {
    public int insert(Order order) {
        System.out.println("Establish order Object success!");
        return 1;
    }
}

To create the IOrderService interface:

public interface IOrderService {
    int createOrder(Order order);
}

Create OrderService implementation class:

public class OrderService implements IOrderService {

    private OrderDao orderDao;

    public OrderService(OrderDao orderDao) {
        orderDao = new OrderDao();
    }

    @Override
    public int createOrder(Order order) {
        System.out.println("OrderService call OrderDao Create order");
        return orderDao.insert(order);
    }
}

Let's use the static agent. The creation time of the completed order is automatically divided by year, and the next code is completed by using the agent object. Create a data source routing object, and use ThreadLocal to implement it. DynamicDataSourceEntity:

public class DynamicDataSourceEntity {

    /**
     * Default data source
     */
    public static final String DEFAULT_DATA_SOURCE = null;

    private static final ThreadLocal<String> local = new ThreadLocal<>();

    private DynamicDataSourceEntity() {}

    /**
     * Get the data source currently in use
     * @return
     */
    public static String get() {
        return local.get();
    }

    /**
     * Set data source with known name
     * @param dataSource
     */
    public static void set(String dataSource) {
        local.set(dataSource);
    }

    /**
     * Restore the data source of the current section
     */
    public static void restore() {
        local.set(DEFAULT_DATA_SOURCE);
    }

    /**
     * Set data source dynamically according to year
     * @param year
     */
    public static void set(int year) {
        local.set("DB_" + year);
    }

    /**
     * Clear data source
     */
    public static void remove() {
        local.remove();
    }

}

Create switch data source proxy OrderServiceStaticProxy:

public class OrderServiceStaticProxy implements IOrderService {

    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

    private IOrderService orderService;

    public OrderServiceStaticProxy(IOrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public int createOrder(Order order) {
        before();
        Long time = order.getCreateTime();
        Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
        System.out.println("Static agent classes are automatically assigned to[ DB_" + dbRouter + "]The data source processes the data.");
        DynamicDataSourceEntity.set(dbRouter);
        orderService.createOrder(order);
        after();
        return 0;
    }

    private void before(){
        System.out.println("Agent method execution started......");
    }
    private void after(){
        System.out.println("Agent method execution is over......");
    }
}

Code of main method:

public static void main(String[] args) throws ParseException {

    Order order = new Order();
    order.setId("010101001");
    //Date today = new Date();
    //order.setCreateTime(today.getTime());

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
    Date date = sdf.parse("2019/02/01");
    order.setCreateTime(date.getTime());


    IOrderService orderService = new OrderServiceStaticProxy(new OrderService());
    orderService.createOrder(order);

}

The operation result is:

In line with our expected results. Now let's review the class diagram to see if it is consistent with the class structure we drew first:

2.2 dynamic agent

The idea of dynamic agent and static agent is basically the same, but the function of dynamic agent is more powerful, and the adaptability is stronger with the expansion of business. For example, if the business of looking for an object develops into an industry, then it is a marriage intermediary. To upgrade the code implementation process to meet the needs of helping more single people find objects. Next, use JDK to implement the marriage agency.

2.2.1 JDK implementation mode

Create a marriage introduction jdkmaria class:

public class JDKMarriage implements InvocationHandler {

    private Object target;

    public Object getInstance(Object target) {
        this.target = target;

        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object object = method.invoke(this.target, args);
        after();
        return object;
    }

    private void before(){
        System.out.println("I'm the marriage agency: to find you a partner, I've got your needs now");
        System.out.println("Start looking for");
    }
    private void after(){
        System.out.println("If it's appropriate, get ready");
    }
}

To create a single Customer class:

public class Customer implements Person {
    @Override
    public void lookForMate() {
        System.out.println("Tall, rich and handsome");
        System.out.println("Height 180 cm");
        System.out.println("There is room and car.");
    }
}

Test main method code:

 public static void main(String[] args) {

    JDKMarriage marriage = new JDKMarriage();

    Person person = (Person) marriage.getInstance(new Customer());
    person.lookForMate();
}

Operation result:

The above dynamic agent case is completed by implementing the InvocationHandler interface. In the previous data source routing business, dynamic agent is also used to implement the following code:

public class OrderServiceDynamicProxy implements InvocationHandler {

    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

    public Object target;

    public Object getInstance(Object target) {
        this.target = target;
        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before(args[0]);
        Object object = method.invoke(target, args);
        after();
        return object;
    }

    public void before(Object target) {
        try {
            System.out.println("Agent method execution started......");
            Long time = (Long)target.getClass().getMethod("getCreateTime").invoke(target);
            Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
            System.out.println("Dynamic agent classes are automatically assigned to[ DB_" + dbRouter + "]Data source processing data");
            DynamicDataSourceEntity.set(dbRouter);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    public void after() {
        System.out.println("Agent method execution is over......");
    }
}

Test main method code:

public static void main(String[] args) throws ParseException {
        Order order = new Order();
        order.setId("010101001");
//        Date today = new Date();
//        order.setCreateTime(today.getTime());

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        Date date = sdf.parse("2019/02/01");
        order.setCreateTime(date.getTime());

        IOrderService orderService = (IOrderService) new OrderServiceDynamicProxy().getInstance(new OrderService());
        orderService.createOrder(order);
}

Operation result:

It can still achieve the desired operation effect. However, after the implementation of dynamic agent, we can not only implement the data source dynamic routing of Order, but also implement the data source routing of any other class.

2.2.2 CGLib proxy calling API and principle analysis

pom dependence:

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

Take the marriage agency as an example to create the CglibMarriage class:

public class CglibMarriage implements MethodInterceptor {

    public Object getInstance(Class<?> clazz) throws Exception {
        Enhancer enhancer = new Enhancer();
        //Which class to set as the parent of the new generated class
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);

        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects,
                            MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o, objects);
        after();
        return obj;
    }

    private void before(){
        System.out.println("I'm the marriage agency: to find you a partner, I've got your needs now");
        System.out.println("Start looking for");
    }
    private void after(){
        System.out.println("If it's appropriate, get ready");
    }
}

Then create the single Customer class Customer:

public class Customer {
    
    public void lookForMate() {
        System.out.println("Tall, rich and handsome");
        System.out.println("Height 180 cm");
        System.out.println("There is room and car.");
    }
}

Note: the target object of CGLib proxy does not need to implement any interface. It implements dynamic proxy by dynamically inheriting the target object. Look at the test code:

public static void main(String[] args) {

        try {
            Customer customer = (Customer)new CglibMarriage().getInstance(Customer.class);
            customer.lookForMate();
        } catch (Exception e) {
            e.printStackTrace();
        }

}

The reason why the method efficiency of CGLib agent executing proxy object is higher than that of JDK is that CGLib adopts FastClass mechanism. The principle of FastClass is: generate a class for proxy class and proxy class respectively, and this class will assign an index(int type) to the method of proxy class or proxy class. This index is used as the input parameter, FastClass can directly locate the method to be called and directly enter Line call, which saves reflection call, so the call efficiency is higher than that of JDK dynamic agent through reflection call.

2.2.3 dynamic agent comparison between cglib and JDK

1, The JDK dynamic proxy implements the interface of the proxy object, and the CGLib proxy inherits the proxy object.

2. Both JDK and CGLib generate bytecode during operation, JDK dynamic agent generates class bytecode directly, CGLib agent generates class bytecode through asm framework, CGLib agent implementation is more complex, and generation agent is inefficient compared with JDK dynamic agent.

3. JDK dynamic proxy calls the proxy method through reflection mechanism, CGLib proxy directly calls the method through FastClass mechanism, and CGLib proxy has high execution efficiency.

3, Spring and agent mode

3.1 application of agent pattern in Spring source code

First, the core method of ProxyFactoryBean is getObject(). Let's look at the source code:

@Nullable
public Object getObject() throws BeansException {
    this.initializeAdvisorChain();
    if (this.isSingleton()) {
        return this.getSingletonInstance();
    } else {
        if (this.targetName == null) {
            this.logger.info("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
        }

        return this.newPrototypeInstance();
    }
}

In the getObject() method, the main calls are getSingletonInstance() and newPrototypeInstance(); In Spring configuration, if no settings are made, the beans generated by Spring proxy are singleton objects. If you modify the scope, a new prototype object is created one at a time. The logic in newPrototypeInstance() is relatively complex. We will do in-depth research later in the course. Here we will do a simple understanding.

3.2 principle of agent selection in spring

Spring uses dynamic proxy to implement AOP. There are two very important classes. One is the JdkDynamicAopProxy class and CglibAopProxy class. Take a look at the class diagram below:

  • When a Bean has an implementation interface, Spring uses JDK's dynamic proxy;

  • When a Bean does not implement an interface, Spring chooses CGLib.

3, The essential difference between static agent and dynamic agent

1. The static agent can only complete the agent operation manually. If the agent class adds new methods, the agent class needs to be added synchronously, which violates the opening and closing principle.

2. The dynamic agent adopts the way of dynamically generating code at runtime, cancels the extension restriction of the proxied class, and follows the opening and closing principle.

3. If the dynamic agent wants to enhance the logic extension of the target class, it only needs to add a new policy class to complete, and does not need to modify the code of the agent class.

4, Advantages and disadvantages of agent mode

Using agent mode has the following advantages:

  • The proxy mode can separate the proxy object from the real target object;

  • To some extent, the coupling degree of the system is reduced and the expansibility is good;

  • It can protect the target object;

  • You can enhance the functionality of the target object.

Of course, the agent mode also has disadvantages:

  • Agent pattern will increase the number of classes in system design;

  • Adding a proxy object to the client and the target will slow down the request processing;

  • It increases the complexity of the system.

Tags: Programming JDK Spring Database Java

Posted on Mon, 24 Feb 2020 04:39:03 -0500 by MattWeet