Design mode of hand roll - agent mode

Posted by Patch^ on Mon, 01 Nov 2021 14:00:25 +0100

1, Introduction to agent mode

1.1 definitions

  • Proxy is a design pattern that provides another access to the target object; That is to access the target object through the proxy object. The advantage of this is that additional function operations can be enhanced on the basis of the implementation of the target object, that is, the function of the target object can be extended.
  • For example, a proxy object is provided to an object, and the proxy object controls the reference to the original object. Generally speaking, the agency model is a common intermediary in our life.

1.2 agent mode classification

We have many different ways to implement agents. If agents are classified according to the creation period, they can be divided into two types: static agents and dynamic agents (jdk, cglib). Static agents are created by programmers or automatically generated by specific tools. Before the program runs, the agent class. Class file has been created. Dynamic proxy is created dynamically by reflection mechanism when the program is running.

1.3 static proxy implementation

General interface definition:

public interface IRun {
    void run();
}

General interface implementation class definition:

public class RealizeRun implements IRun {
    @Override
    public void run() {
        System.out.println("Executing main stream save database");
    }
}

General interface proxy class definition:

public class StaticProxy implements IRun {

    private IRun run;

    public StaticProxy(IRun run) {
        super();
        this.run = run;
    }

    @Override
    public void run() {
        begin();
        run.run();
        end();
    }
    public void end() {
        System.out.println("static-agent-Open transaction");
    }
    public void begin() {
        System.out.println("static-agent-Commit transaction");
    }
}

Expected results:

static-agent-Commit transaction
 Executing main stream save database
static-agent-Open transaction

1.4 dynamic agent implementation

1.4.1 Jdk agent (based on interface agent)

jdk dynamic proxy factory class definition:

public class ProxyJdkFactory implements InvocationHandler {

    //Define target object
    private Object target;

    public ProxyJdkFactory(Object target) {
        this.target = target;
    }

    /**
     * Gets the proxy interface instance object
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        begin();
        Object invoke = method.invoke(target, args);
        end();
        return invoke;
    }
    private void begin() {
        System.out.println("jdk-agent-Open transaction");
    }
    private void end() {
        System.out.println("jdk-agent-Commit transaction");
    }
}

Expected operation results:

jdk-agent-Open transaction
 Executing main stream save database
jdk-agent-Commit transaction

1.4.2 Cglib dynamic agent (based on implementation class agent)

Environmental dependency:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
        <version>2.4.5</version>
    </dependency>

Cglib dynamic agent factory definition:

public class ProxyCglibFactory implements MethodInterceptor {

    //Target object of proxy
    private Object target;

    public ProxyCglibFactory(Object target) {
        this.target = target;
    }

    /**
     * Gets the proxy class instance object
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        //Cglib get proxy object tool
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return (T) enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        begin();
        Object invoke = method.invoke(target, args);
        end();
        return invoke;
    }

    private void begin() {
        System.out.println("cglib-agent-Start transaction");
    }
    private void end() {
        System.out.println("cglib-agent-Commit transaction");
    }
}

Expected operation results:

cglib-agent-Start transaction
 Executing main stream save database
cglib-agent-Commit transaction

2, Proxy mode application scenario

2.1 scenario example:

  1. Your database access layer often provides a more basic application to reduce the number of database connections during application service expansion.
  2. Some middleware used, such as; In the RPC framework, after getting the description of the interface in the jar package, the middleware will generate the corresponding proxy class when the service is started. When calling the interface, it actually passes through the socket information sent by the proxy class.
  3. In addition, like our commonly used MyBatis, it basically defines interfaces, but does not need to write implementation classes. You can add, delete, modify and query sql statements in xml or user-defined annotations.

2.2 scenario use case simulation diagram:

2.3 proxy mode simulation scenario

The proxy mode is used to simulate the proxy process of a class in Mybatis, that is, only the interface needs to be defined, it can be associated with the sql statement in the method annotation to complete the operation on the database.
Note:

  • BeanDefinitionRegistryPostProcessor, the interface class of spring, is used to handle the definition registration of bean s.
  • GenericBeanDefinition, which defines bean information, is used in mybatis spring; ScannedGenericBeanDefinition is slightly different.
  • FactoryBean, a class used to process bean factories. This class is very common.

2.4 agent mode middleware model structure

  • There are not many classes involved in this model, but they are all extracted core processing classes. The main thing is to proxy classes and register them in spring.
  • The top of the figure above is about the implementation of middleware, and the following corresponds to the use of functions.

2.5 scenario code implementation

Annotation definition:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
    /**
     * sql sentence
     * @return
     */
    String value() default "";
}

Query dao interface definition:

public interface IUserDao {
    @Select("select username from t_user where id = #{uId}")
    String queryUserInfo(String uId);
}

Mapper factory proxy class:

@Slf4j
public class MapperFactoryBean<T> implements FactoryBean<T> {

    private Class<T> mapperInterface;

    public MapperFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @Override
    public T getObject() throws Exception {
        InvocationHandler handler = (proxy, method, args) -> {
            Select annotation = method.getAnnotation(Select.class);
            log.info("SQL: {}", annotation.value().replace("#{uId}", args[0].toString()));
            return args[0] + ",Agent mode Practice";
        };
        return (T) Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{mapperInterface}, handler);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }
    /**
     * Object is a singleton
     *
     * @return
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}
  • If you have read the source code of mybatis, you can see such a class; MapperFactoryBean, here we also simulate such a class to implement our definition of proxy class.
  • By inheriting FactoryBean, provide bean objects, that is, methods; T getObject().
    The method getObject() provides the proxy of the class and simulates the processing of sql statements, which includes the processing logic when users call dao layer methods.
  • In addition, at the top, we provide a constructor to pass through the proxy class, class mapperinterinterface, which is also used in mybatis.
  • In addition, getObjectType() provides object type feedback, and the return class of isSingleton() is singleton

Register Bean factory definition:

/**
 * Description: Register the Bean into the Spring container
 * <br/>
 * RegisterBeanFactory
 *
 * @author laiql
 * @date 2021/11/1 11:31
 */
public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        //Generate Bean definition
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(MapperFactoryBean.class);
        beanDefinition.setScope("singleton");
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(IUserDao.class);

        //Define Bean holding objects
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDao");
        //Bean registration
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, beanDefinitionRegistry);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    }
}
  • Here, we hand over the proxy bean to the spring container for management, which is very convenient for us to obtain the proxy bean. This part is the source code of a bean registration process in spring.
  • GenericBeanDefinition is used to define the basic information of a bean setBeanClass(MapperFactoryBean.class);, It also includes addGenericArgumentValue(IUserDao.class) that can be passed to the constructor;
  • Finally, use BeanDefinitionReaderUtils.registerBeanDefinition to register the bean, that is, to register in the DefaultListableBeanFactory.

Profile definition:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
       default-autowire="byName">
    <bean id="userDao" class="com.smartfrank.pattern.proxy.example.RegisterBeanFactory"/>
</beans>

Test case definition:

@Slf4j
public class ProxyTest {
    @Test
    public void proxyTest() {
        BeanFactory factory = new ClassPathXmlApplicationContext("spring-config.xml");
        IUserDao userDao = factory.getBean("userDao", IUserDao.class);
        String userInfo = userDao.queryUserInfo("10001");
        log.info("Expected test verification results: {}", userInfo);

    }
}

Expected test run results:

13:44:25.153 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@6e4784bc
13:44:25.306 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 1 bean definitions from class path resource [spring-config.xml]
13:44:25.340 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'userDao'
13:44:25.379 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Overriding bean definition for bean 'userDao' with a different definition: replacing [Generic bean: class [com.smartfrank.pattern.proxy.example.RegisterBeanFactory]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [spring-config.xml]] with [Generic bean: class [com.smartfrank.pattern.proxy.example.MapperFactoryBean]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
13:44:25.388 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'userDao'
13:44:25.412 [main] INFO com.smartfrank.pattern.proxy.example.MapperFactoryBean - SQL: select username from t_user where id = 10001
13:44:25.413 [main] INFO com.smartfrank.pattern.proxy.ProxyTest - Expected test verification results: 10001,Agent mode Practice
Disconnected from the target VM, address: '127.0.0.1:60233', transport: 'socket'

2.6 advantages of agent mode

1. Proxy mode can separate the proxy object from the real called target object.
2. It reduces the coupling degree of the system to a certain extent and has good expansibility.
3. It can protect the target object.
4. You can enhance the functionality of the target object.

2.7 disadvantages of agent mode

1. The agent pattern will increase the number of classes in the system design.
2. Adding a proxy object between the client and the target object will slow down the request processing speed.
3. It increases the complexity of the system.

3, Summary

  • For the explanation of this part of the proxy mode, we use the development of some core functions in mybatis spring middleware to reflect the power of the proxy mode. Therefore, some knowledge points related to the creation of proxy classes and the registration of bean s in spring may be rarely used in ordinary business development, But it is a very common operation in middleware development.
  • The design method of proxy mode can make the code cleaner, cleaner and easy to maintain. Although many additional classes are added in this part of development, including the registration of bean s, such middleware is highly reusable and more intelligent, and can be easily extended to various service applications.

Case code address

Topics: Design Pattern