MyBatis12-"General Source Code Guide: Detailed Explanation of MyBatis Source Code" Notes-session, plugin package

This series of articles is my notes and summary from the book "General Source Code Guide: Detailed Explanation of MyBatis Source Code
" . This book is based on MyBatis-3.5.2 version. The author of the book is Brother Yi . The link is Brother Yi's Weibo in CSDN. But among all the articles I read, there was only one that briefly introduced this book. It doesn’t reveal too much about the charm of the book. Next, I will record my learning summary. If the author thinks that I have infringed the copyright, please contact me to delete it. Thanks again to Brother Yi for providing learning materials. This explanation will accompany the entire series of articles. Respect the originality . I have purchased the revised book on WeChat Reading.
Copyright statement: This article is an original article by CSDN blogger "Architect Yi Ge" and follows the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this statement when reprinting.
Original link: https://blog.csdn.net/onlinedct/article/details/107306041

The session package is the external interface package of the entire MyBatis application and is the package closest to the user.

    public static void main(String[] args) {
    
    
        // 第一阶段:MyBatis的初始化阶段
        String resource = "mybatis-config.xml";
        // 得到配置文件的输入流
        InputStream inputStream = null;
        try {
    
    
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        // 得到SqlSessionFactory
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);

        // 第二阶段:数据读写阶段
        try (SqlSession session = sqlSessionFactory.openSession()) {
    
    
            // 找到接口对应的实现
            UserMapper userMapper = session.getMapper(UserMapper.class);
            // 组建查询参数
            User userParam = new User();
            userParam.setSchoolName("Sunny School");
            // 调用接口展开数据库操作
            List<User> userList =  userMapper.queryUserBySchoolName(userParam);
            // 打印查询结果
            for (User user : userList) {
    
    
                System.out.println("name : " + user.getName() + " ;  email : " + user.getEmail());
            }
        }
    }

The SqlSessionFactory class and SqlSession class involved in the code are all classes in the session package. Through these classes, MyBatis can be triggered to expand the database operation. This also verifies the conclusion that the session package is the external interface package of the entire MyBatis.

1.SqlSession and its related classes

When performing query operations, you only need to deal with the SqlSession object. The SqlSession object is produced by SqlSessionFactory, and SqlSessionFactory is created by SqlSessionFactoryBuilder.
Insert image description here

1.1.SqlSession generation chain

SqlSession and its related classes form a generation chain. SqlSessionFactoryBuilder generated SqlSessionFactory, and SqlSessionFactory generated SqlSession.
The SqlSessionFactoryBuilder class is the builder class of SqlSessionFactory, which can create SqlSessionFactory objects based on the configuration file. Let's look at a core build method in the SqlSessionFactoryBuilder class.

  /**
   * 建造一个SqlSessionFactory对象
   * @param reader 读取字符流的抽象类
   * @param environment 环境信息
   * @param properties 配置信息
   * @return SqlSessionFactory对象
   */
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    
    
    try {
    
    
      // 传入配置文件,创建一个XMLConfigBuilder类
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      // 分两步:
      // 1、解析配置文件,得到配置文件对应的Configuration对象
      // 2、根据Configuration对象,获得一个DefaultSqlSessionFactory
      return build(parser.parse());
    } catch (Exception e) {
    
    
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
    
    
      ErrorContext.instance().reset();
      try {
    
    
        reader.close();
      } catch (IOException e) {
    
    
      }
    }
  }

The process of creating a SqlSessionFactory object is mainly divided into three steps:

  1. Pass in the configuration file and create an XMLConfigBuilder class to prepare for parsing the configuration file.
  2. Parse the configuration file and obtain the Configuration object corresponding to the configuration file.
  3. According to the Configuration object, obtain a DefaultSqlSessionFactory.

The DefaultSqlSessionFactory object can create an object of the DefaultSqlSession class, a subclass of SqlSession. This process is completed by the openSessionFromDataSource method:

  /**
   * 从数据源中获取SqlSession对象
   * @param execType 执行器类型
   * @param level 事务隔离级别
   * @param autoCommit 是否自动提交事务
   * @return SqlSession对象
   */
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    
    
    Transaction tx = null;
    try {
    
    
      // 找出要使用的指定环境
      final Environment environment = configuration.getEnvironment();
      // 从环境中获取事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 从事务工厂中生产事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 创建执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 创建DefaultSqlSession对象
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
    
    
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
    
    
      ErrorContext.instance().reset();
    }
  }

1.2.DefaultSqlSession类

We have already said that the session package is the external interface package of the entire MyBatis application, and the executor package is the core executor package. The main work done by the DefaultSqlSession class is very simple - handing over the work of the interface package to the executor package.

public class DefaultSqlSession implements SqlSession {
    
    
  // 配置信息
  private final Configuration configuration;
  // 执行器
  private final Executor executor;
  // 是否自动提交
  private final boolean autoCommit;
  // 缓存是否已经被污染
  private boolean dirty;
  // 游标列表
  private List<Cursor<?>> cursorList;

The properties of the DefaultSqlSession class contain an Executor object, and the DefaultSqlSession class hands over all main operations to the Executor object in the property. Taking the selectList method as an example, related database query operations are completed by the query method of the Executor object.

  /**
   * 查询结果列表
   * @param <E> 返回的列表元素的类型
   * @param statement SQL语句
   * @param parameter 参数对象
   * @param rowBounds  翻页限制条件
   * @return 结果对象列表
   */
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    
    
    try {
    
    
      // 获取查询语句
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 交由执行器进行查询
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
    
    
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
    
    
      ErrorContext.instance().reset();
    }
  }

1.3.SqlSessionManager class

Among the related classes of SqlSession, SqlSessionManager implements both the SqlSessionFactory interface and the SqlSession interface.
Insert image description here
This kind of class that implements both the factory interface and the factory product interface is rare. Therefore, let's study separately how the SqlSessionManager class is implemented and the significance of its existence.

public class SqlSessionManager implements SqlSessionFactory, SqlSession {
    
    

  // 构造方法中传入的SqlSessionFactory对象
  private final SqlSessionFactory sqlSessionFactory;
  // 在构造方法中创建的SqlSession代理对象
  private final SqlSession sqlSessionProxy;
  // 该变量用来存储被代理的SqlSession对象
  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();

  /**
   * SqlSessionManager构造方法
   * @param sqlSessionFactory SqlSession工厂
   */
  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    
    
    this.sqlSessionFactory = sqlSessionFactory;
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{
    
    SqlSession.class},
        new SqlSessionInterceptor());
  }

SqlSessionManager creates a proxy object of SqlSession in the construction method, which can intercept the methods of the proxy object. The intercepted method will be handed over to the invoke method of the SqlSessionInterceptor internal class for processing.

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
      // 尝试从当前线程中取出SqlSession对象
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) {
    
     // 当前线程中确实取出了SqlSession对象
        try {
    
    
          // 使用取出的SqlSession对象进行操作
          return method.invoke(sqlSession, args);
        } catch (Throwable t) {
    
    
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {
    
     // 当前线程中还没有SqlSession对象
        // 使用属性中的SqlSessionFactory对象创建一个SqlSession对象
        try (SqlSession autoSqlSession = openSession()) {
    
    
          try {
    
    
            // 使用新创建的SqlSession对象进行操作
            final Object result = method.invoke(autoSqlSession, args);
            autoSqlSession.commit();
            return result;
          } catch (Throwable t) {
    
    
            autoSqlSession.rollback();
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
      }
    }

It can be seen that when the proxy object of SqlSession intercepts the method, it will try to retrieve a SqlSession object from the ThreadLocal of the current thread.

  • If a SqlSession object exists in ThreadLocal, the proxy object will hand over the operation to the retrieved SqlSession object for processing.
  • If the SqlSession object does not exist in ThreadLocal, use the SqlSessionFactory object in the property to create a SqlSession object, and then the proxy object hands over the operation to the newly created SqlSession object for processing.

The meaning of each attribute of SqlSessionManager is also clear

  // 构造方法中传入的SqlSessionFactory对象
  private final SqlSessionFactory sqlSessionFactory;
  // 在构造方法中创建的SqlSession代理对象
  private final SqlSession sqlSessionProxy;
  // 该变量用来存储被代理的SqlSession对象
  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();

After understanding the meaning of the main methods and properties of SqlSessionManager, its structure is very clear, so what is the meaning of its existence? After all, DefaultSqlSessionFactory as a factory or DefaultSqlSession as a product can achieve its functions. In fact, SqlSessionManager provides the following two functions after integrating factories and products.

  • SqlSessionManager can always give a product (taken from the thread ThreadLocal or create a new one) and use the product to complete related operations. External users do not need to know the details, so the process of calling the factory to produce the product is omitted.
  • Provides product reuse functionality. The products produced by the factory can be put into the thread ThreadLocal and saved (the startManagedSession method needs to be explicitly called), thereby realizing product reuse. This not only ensures thread safety but also improves efficiency.

In many scenarios, users use products produced in factories and do not care whether the products are produced immediately or cached after previous production. In this case, you can refer to the design of SqlSessionManager to provide a more efficient way of presenting products. In the process of reading the source code, we may not be able to know the functions of some classes in advance. At this time, you need to read its source code first, and then guess its functions based on the source code. This method of reading source code is more time-consuming and laborious, but sometimes it is unavoidable. We adopted this approach when reading the SqlSessionManager source code.

2.Configuration class

We know that the configuration file mybatis-config.xml is the main entry point for MyBatis configuration, including the path to the mapping file, which is also specified through it. The root node of the configuration file is the configuration node, so all configuration information is stored in this node.

The information of the configuration node is parsed and stored in the Configuration object, so the Configuration object contains all the configuration information for MyBatis operation.

And the Configuration class further processes the configuration information, sets default values ​​for many configuration items, and defines aliases for many entities. Therefore, the Configuration class is an extremely important class in MyBatis.

/**
 * 主要内容分为以下几个部分:
 * 1、大量的配置项,和与`<configuration>`标签中的配置对应
 * 2、创建类型别名注册机,并向内注册了大量的类型别名
 * 3、创建了大量Map,包括存储映射语句的Map,存储缓存的Map等,这些Map使用的是一种不允许覆盖的严格Map
 * 4、给出了大量的处理器的创建方法,包括参数处理器、语句处理器、结果处理器、执行器。
 *    注意这里并没有真正创建,只是给出了方法。
 */

public class Configuration {
    
    

  // <environment>节点的信息
  protected Environment environment;

  // 以下为<settings>节点中的配置信息
  protected boolean safeRowBoundsEnabled;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase;
  protected boolean aggressiveLazyLoading;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  protected boolean returnInstanceForEmptyRow;

  protected String logPrefix;
  protected Class<? extends Log> logImpl;
  protected Class<? extends VFS> vfsImpl;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  protected ResultSetType defaultResultSetType;
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
  // 以上为<settings>节点中的配置信息

  // <properties>节点信息
  protected Properties variables = new Properties();
  // 反射工厂
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  // 对象工厂
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  // 对象包装工厂
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
  // 是否启用懒加载,该配置来自<settings>节点
  protected boolean lazyLoadingEnabled = false;
  // 代理工厂
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
  // 数据库编号
  protected String databaseId;
  // 配置工厂,用来创建用于加载反序列化的未读属性的配置。
  protected Class<?> configurationFactory;
  // 映射注册表
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  // 拦截器链(用来支持插件的插入)
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  // 类型处理器注册表,内置许多,可以通过<typeHandlers>节点补充
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  // 类型别名注册表,内置许多,可以通过<typeAliases>节点补充
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  // 语言驱动注册表
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  // 映射的数据库操作语句
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  // 缓存
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  // 结果映射,即所有的<resultMap>节点
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  // 参数映射,即所有的<parameterMap>节点
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  // 主键生成器映射
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
  // 载入的资源,例如映射文件资源
  protected final Set<String> loadedResources = new HashSet<>();
  // SQL语句片段,即所有的<sql>节点
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

  // 暂存未处理完成的一些节点
  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

  // 用来存储跨namespace的缓存共享设置
  protected final Map<String, String> cacheRefMap = new HashMap<>();

Nearly 20 classes such as BaseBuilder, BaseExecutor, Configuration, and ResultMap in MyBatis all reference the Configuration object in their attributes, which makes the Configuration object a globally shared configuration information center for MyBatis and can provide configuration information query and update services for other objects. .

The Configuration class is a parsing entity class set up to save configuration information. Although there are many member variables, the member methods are very simple and will not be introduced further.

In order to facilitate quick query of configuration information, an internal class StrictMap is also set up in the Configuration class. StrictMap is a subclass of HashMap and has the following characteristics.

  • Overwriting key values ​​is not allowed. That is, if the key to be stored already exists in StrictMap, an exception will be thrown directly. This eliminates the confusion of configuration information due to overwriting.
  • Automatically attempts to save the given data again using the short name. For example, if you store data with the key "com.github.yeecode.clazzName" into StrictMap, in addition to storing the data, StrictMap will also store another copy with "clazzName" as the key (if the short name "clazzName" without causing ambiguity). This enables configuration information to be queried by a short name (if the short name does not cause ambiguity).
    /**
     * 向Map中写入键值对
     * @param key 键
     * @param value 值
     * @return 旧值,如果不存在旧值则为null。因为StrictMap不允许覆盖,则只能返回null
     */
    @Override
    @SuppressWarnings("unchecked")
    public V put(String key, V value) {
    
    
      if (containsKey(key)) {
    
    
        //如果已经存在此key了,直接报错
        throw new IllegalArgumentException(name + " already contains value for " + key
            + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
      }
      if (key.contains(".")) {
    
    
        // 例如key=“com.github.yeecode.clazzName”,则shortName = “clazzName”,即获取一个短名称
        final String shortKey = getShortName(key);
        if (super.get(shortKey) == null) {
    
    
          // 以短名称为键,放置一次
          super.put(shortKey, value);
        } else {
    
    
          // 放入该对象,表示短名称会引发歧义
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      // 以长名称为键,放置一次
      return super.put(key, value);
    }

3.plugin package

MyBatis also provides plug-in functionality, allowing other developers to develop plug-ins for MyBatis to extend the functionality of MyBatis. Classes related to plug-ins are in the plugin package of MyBatis. Read the source code of the plugin package, learn how to develop MyBatis plug-ins, and analyze the MyBatis plug-in insertion and management mechanism through the source code.

3.1.Chain of responsibility model

In some scenarios, a target object may need to be processed by multiple objects. For example, if we want to organize a campus party, we need to prepare the actors as follows.

  • Send an email to the actors to inform them of the time and location of the party. This task is the responsibility of the email sender.
  • Preparing clothes for actors according to their gender is the responsibility of the material manager.
  • If the actor is underage, school bus transportation will be arranged for him or her, and this task will be the responsibility of the external liaison officer.

Show this process with code:

// 不使用责任链模式
System.out.println("不使用责任链模式:");
// 创建三个工作人员实例
MailSender mailSender = new MailSender();
MaterialManager materialManager = new MaterialManager();
ContactOfficer contactOfficer = new ContactOfficer();
// 依次处理每个参与者
for (Performer performer : performerList) {
    
    
    System.out.println("process " + performer.getName() + ":");
    new MailSender().handle(performer);
    new MaterialManager().handle(performer);
    new ContactOfficer().handle(performer);
    System.out.println("---------");
}

The chain of responsibility model assembles multiple processors into a chain. After the processed object is placed at the beginning of the chain, it will automatically be passed and processed throughout the chain. In this way, the object being processed does not need to deal with each processor, nor does it need to understand the transmission process of the entire chain, so the decoupling of the object being processed from a single processor is achieved.

// 使用责任链模式
System.out.println("使用责任链模式:");
// 创建责任链
Handler handlerChain = new MailSender();
handlerChain.setNextHandler(new MaterialManager()).setNextHandler(new ContactOfficer());

// 依次处理每个参与者
for (Performer performer : performerList) {
    
    
    System.out.println("process " + performer.getName() + ":");
    handlerChain.triggerProcess(performer);
    System.out.println("---------");
}

In this way, each actor does not need to deal directly with the staff, nor does he need to care about how many staff members are in the chain of responsibility. The chain of responsibility model not only reduces the coupling between the processed object and the processor, but also allows us to assemble the processing process more flexibly. For example, we can easily add or delete processors or adjust the order of processors in the chain of responsibility.

3.2.MyBatis plug-in development

To understand the source code of a functional module, a simple way is to first learn to use this module. We develop a MyBatis plug-in with very simple functions to understand the development process of the MyBatis plug-in. The function of the plug-in we want to develop is: when MyBatis queries the results in list form, print out the number of results.
The source code of the entire plug-in is very simple

@Intercepts({
    
    
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {
    
    Statement.class})
})
public class YeecodeInterceptor implements Interceptor {
    
    
    private String info;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    
        // 执行原有方法
        Object result = invocation.proceed();
        // 打印原方法输出结果的数目
        System.out.println(info + ":" + ((List) result).size());
        // 返回原有结果
        return result;
    }

    @Override
    public void setProperties(Properties properties) {
    
    
        // 为拦截器设置属性
        info = properties.get("preInfo").toString();
    }
}

The MyBatis plug-in is a class that implements the Interceptor interface. Interceptor means interceptor, so the real name of the MyBatis plug-in we are talking about is MyBatis interceptor. Since there is also a class called Plugin in the plugin package, in order to avoid confusion, in the following description, we use interceptor to refer to the plug-in class we wrote.

There is an annotation Intercepts on the interceptor class, and the parameter of Intercepts is an array of Signature annotations. Each Signature annotation declares the method to be intercepted by the current interceptor class. The meanings of the parameters in the Signature annotation are as follows.

  • type: The type to be intercepted by the interceptor. The type to be intercepted by the YeecodeInterceptor interceptor is the ResultSetHandler type.
  • method: The method in the type type that the interceptor wants to intercept. What the YeecodeInterceptor interceptor intercepts is the handleResultSets method in the ResultSetHandler type.
  • args: The parameter type list of the method method in the type type to be intercepted by the interceptor. In the YeecodeInterceptor interceptor, the handleResultSets method in the ResultSetHandler type has only one parameter of Statement type.

When you want to intercept multiple methods, just put multiple Signature annotations in the Intercepts array.
There are three methods in the Interceptor interface for the interceptor class to implement. The meanings of these three methods are as follows.

  • intercept: The interceptor class must implement this method. When the interceptor intercepts the target method, it will transfer the operation to the intercept method, and the parameter Invocation is the intercepted target method. In the intercept method of the YeecodeInterceptor interceptor, the original method will be executed first and the output result of the original method will be obtained, then the number of output results of the original method will be printed out, and finally the original result will be returned. In this way, the YeecodeInterceptor interceptor realizes the function of printing the number of results.
  • plugin: The interceptor class can choose to implement this method. This method can output an object to replace the target object passed in as the input parameter. In the YeecodeInterceptor interceptor, we did not implement this method, but directly used the default implementation in the Interceptor interface. In the default implementation, the Plugin.wrap method is called to give a wrapping object of the original object, and then the original object is replaced with this object.
  • setProperties: Interceptor classes can choose to implement this method. This method is used to set properties for the interceptor. In the YeecodeInterceptor interceptor, we use this method to set the value of the info attribute for the interceptor.

After the interceptor configuration is completed, the interceptor needs to be set to the MyBatis configuration to take effect.

 <plugin interceptor="com.github.yeecode.mybatisdemo.plugin.YeecodeInterceptor">
     <property name="preInfo" value="本次查询记录数目"/>
 </plugin>

In this way, we have completed the development, configuration and use of a simple MyBatis interceptor. The development of MyBatis interceptors is still very easy to get started. You can develop some interceptors according to actual needs in daily use to expand the functions of MyBatis.

4.MyBatis interceptor platform

In order to facilitate developers to develop interceptors for MyBatis, MyBatis has built an interceptor platform in the plugin package.
Insert image description here
The core class in the entire class diagram is the Plugin class, which inherits the java.lang.reflect.InvocationHandler interface and is therefore a proxy class based on reflection.

public class Plugin implements InvocationHandler {
    
    
  // 被代理对象
  private final Object target;
  // 拦截器
  private final Interceptor interceptor;
  // 拦截器要拦截的所有的类,以及类中的方法
  private final Map<Class<?>, Set<Method>> signatureMap;

The signatureMap attribute of the Plugin class stores the classes and methods to be intercepted by the current interceptor. This information is obtained from the Intercepts annotation and Signature annotation of the interceptor through the getSignatureMap method.

  // 得到该拦截器interceptor要拦截的类型与方法。Map<Class<?>, Set<Method>> 中键为类型,值为该类型内的方法集合
  /**
   * 获取拦截器要拦截的所有类和类中的方法
   * @param interceptor 拦截器
   * @return 入参拦截器要拦截的所有类和类中的方法
   */
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    
    
    // 获取拦截器的Intercepts注解
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
    
    
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    }
    // 将Intercepts注解的value信息取出来,是一个Signature数组
    Signature[] sigs = interceptsAnnotation.value();
    // 将Signature数组数组放入一个Map中。键为Signature注解的type类型,值为该类型下的方法集合
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    for (Signature sig : sigs) {
    
    
      Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
      try {
    
    
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
    
    
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }

After having the type information to be intercepted by the interceptor, the Plugin can determine whether the current type needs to be intercepted by the interceptor. If a class needs to be intercepted, the Plugin will create a proxy class for this class. This part of the operation is completed in the wrap method:

  /**
   * 根据拦截器的配置来生成一个对象用来替换被代理对象
   * @param target 被代理对象
   * @param interceptor 拦截器
   * @return 用来替换被代理对象的对象
   */
  public static Object wrap(Object target, Interceptor interceptor) {
    
    
    // 得到拦截器interceptor要拦截的类型与方法
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    // 被代理对象的类型
    Class<?> type = target.getClass();
    // 逐级寻找被代理对象类型的父类,将父类中需要被拦截的全部找出
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    // 只要父类中有一个需要拦截,说明被代理对象是需要拦截的
    if (interfaces.length > 0) {
    
    
      // 创建并返回一个代理对象,是Plugin类的实例
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    // 直接返回原有被代理对象,这意味着被代理对象的方法不需要被拦截
    return target;
  }

Therefore, if a target class needs to be intercepted by an interceptor, then the object of this class has been replaced with a proxy object, that is, the Plugin object in the warp method. When the method of the target class is triggered, it will directly enter the invoke method of the Plugin object. In the invoke method, further judgment will be made at the method level: if the interceptor declares that this method is to be intercepted, the method is handed over to the interceptor for execution; if the interceptor does not declare to intercept this method, the method is handed over to the proxy Object completed.

  /**
   * 代理对象的拦截方法,当被代理对象中方法被触发时会进入这里
   * @param proxy 代理类
   * @param method 被触发的方法
   * @param args 被触发的方法的参数
   * @return 被触发的方法的返回结果
   * @throws Throwable
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    try {
    
    
      // 获取该类所有需要拦截的方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
    
    
        // 该方法确实需要被拦截器拦截,因此交给拦截器处理
        return interceptor.intercept(new Invocation(target, method, args));
      }
      // 这说明该方法不需要拦截,交给被代理对象处理
      return method.invoke(target, args);
    } catch (Exception e) {
    
    
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

Therefore, the Plugin class completes the filtering work at the two levels of class level and method level.

  • If the class to which the target object belongs is intercepted by an interceptor declaration, the Plugin replaces the target object with its own instance as the proxy object.
  • If the method called by the target object is intercepted by the interceptor declaration, the Plugin will hand over the method to the interceptor for processing. Otherwise, the Plugin hands the method to the target object for processing.

Because the Plugin class has completed a lot of work, the work that the interceptor itself needs to do is very simple, which is mainly divided into two parts: using the Intercepts annotation and the Signature annotation to declare the types and methods it wants to intercept; and handling the intercepted through the intercept method. Methods.

Of course, the interceptor can also override the plugin method in the Interceptor interface to achieve more powerful functions.

After overriding the plugin method, you can give another class in the plugin method to replace the target object (without calling the warp method of the Plugin class). In this way, you can completely separate from the Plugin class to complete some more free operations. In this case, how to replace the target object and the processing logic after replacement are completely controlled by the plug-in developer.

5. MyBatis interceptor chain and interception points

From the previous section, we learned how interceptors work. So does MyBatis support configuring multiple interceptors?
The answer is yes. We can configure multiple plug-ins in the MyBatis configuration file, and these plug-ins will be written to the interceptors list of the InterceptorChain class in sequence during the initialization phase of MyBatis. This process is carried out in the pluginElement method of the XMLConfigBuilder class:

  /**
   * 解析<plugins>节点
   * @param parent <plugins>节点
   * @throws Exception
   */
  private void pluginElement(XNode parent) throws Exception {
    
    
    if (parent != null) {
    
     // <plugins>节点存在
      for (XNode child : parent.getChildren()) {
    
     // 依次<plugins>节点下的取出每个<plugin>节点
        // 读取拦截器类名
        String interceptor = child.getStringAttribute("interceptor");
        // 读取拦截器属性
        Properties properties = child.getChildrenAsProperties();
        // 实例化拦截器类
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        // 设置拦截器的属性
        interceptorInstance.setProperties(properties);
        // 将当前拦截器加入到拦截器链中
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

These interceptors form an interceptor chain in the list. The interceptor is implemented by replacing the target object (usually based on the Plugin class, using a dynamic proxy object to replace the target object), so can any object in MyBatis be replaced?

the answer is negative. There are only four classes of objects in MyBatis that can be replaced by interceptors, which are ParameterHandler, ResultSetHandler, StatementHandler and Executor. And replacement can only occur at fixed places, which we call interception points. Taking the ParameterHandler object as an example

  /**
   * 创建参数处理器
   * @param mappedStatement SQL操作的信息
   * @param parameterObject 参数对象
   * @param boundSql SQL语句信息
   * @return 参数处理器
   */
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    
    
    // 创建参数处理器
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 将参数处理器交给拦截器链进行替换,以便拦截器链中的拦截器能注入行为
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    // 返回最终的参数处理器
    return parameterHandler;
  }

At the interception point of the ParameterHandler object, the ParameterHandler object is passed as a parameter to the pluginAll method of the interceptor chain so that the interceptors in the interceptor chain can inject behavior into the ParameterHandler object.

    /**
     * 向所有的拦截器链提供目标对象,由拦截器链给出替换目标对象的对象
     * @param target 目标对象,是MyBatis中支持拦截的几个类(ParameterHandler、ResultSetHandler、StatementHandler、Executor)的实例
     * @return 用来替换目标对象的对象
     */
    public Object pluginAll(Object target) {
    
    
        // 依次交给每个拦截器完成目标对象的替换工作
        for (Interceptor interceptor : interceptors) {
    
    
            target = interceptor.plugin(target);
        }
        return target;
    }

In the pluginAll method of the InterceptorChain class, the target object is handed over to each interceptor in turn for replacement processing (usually the target object is further packaged to inject the interceptor function), and the final target object target brings together the interceptors. The function of each interceptor in the chain is actually the chain of responsibility model. In this way, when the program is running, each interceptor in the interceptor chain will play its role in turn.

Guess you like

Origin blog.csdn.net/d495435207/article/details/131344960