H__D  

MyBatis插件开发原理

  MyBatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变MyBatis的默认行为(诸如SQL重写之类的),由于插件会深入到MyBatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。

  MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现目标对象在执行目标方法之前进行拦截的效果。
  插件介入指的是:创建过程中都会涉及到调用interceptChain.pluginAll()方法对四大对象进行重新包装,返回一个代理对象。
  MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用,默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  1、拦截执行器的方法:Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  2、拦截参数的处理:ParameterHandler(getParameterObject, setParameters)
  3、拦截结果集的处理:ResultSetHandler(handleResultSets, handleOutputParameters)
  4、拦截Sql语法构建的处理:StatementHandler(prepare, parameterize, batch, update, query)

  默认情况下,Mybatis允许使用插件来拦截的接口和方法包括以下几个:

  插件开发是基于动态代理实现的,所有有必要对动态代理有所了解。

  我们可以看一下MyBatis是怎么创建这四大接口对象的。找到源码BaseStatementHandler  

1 this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
2 this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

  进入configuration类,下面几处都是在创建newParameterHandler,ResultSetHandler,StatementHandler这几个对象,在调用的过程中,大家都看到了都使用了interceptorChain.pluginAll方法分别对每一个对象进行了重新包装并返回

 1 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
 2     ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
 3     parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
 4     return parameterHandler;
 5 }
 6  
 7 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
 8       ResultHandler resultHandler, BoundSql boundSql) {
 9     ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
10     resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
11     return resultSetHandler;
12 }
13  
14 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
15     StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
16     statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
17     return statementHandler;
18 }

  点进interceptorChain.pluginAll方法里面

 1 /**
 2 *每一个拦截器对目标类都进行一次代理
 3 *@target
 4 *@return 层层代理后的对象
 5 **/
 6 public Object pluginAll(Object target) {
 7     for (Interceptor interceptor : interceptors) {
 8       target = interceptor.plugin(target);
 9     }
10     return target;
11 
12 }

  这一段代码可以看到:获取所有的Interceptor(拦截器),我们如果需要自定义拦截器就得实现Interceptor这个接口。然后调用interceptor.plugin(target);返回target包装之后的对象。

  所以,我们可以使用插件为目标对象创建一个代理对象,这Spring的AOP一样,其实都是动态代理,面向切面的编程。

MyBatis插件开发 

  下面我们通过案例为StatementHandler创建代理对象

  1、新建一个maven工程,引入mybatis依赖及相关数据库依赖

    

  2、新建一个MyFirstPlugin拦截器类,并且实现Interceptor接口

 1 package com.test.mybatis.plugin;
 2 
 3 import java.util.Properties;
 4 
 5 import org.apache.ibatis.executor.statement.StatementHandler;
 6 import org.apache.ibatis.plugin.Interceptor;
 7 import org.apache.ibatis.plugin.Intercepts;
 8 import org.apache.ibatis.plugin.Invocation;
 9 import org.apache.ibatis.plugin.Plugin;
10 import org.apache.ibatis.plugin.Signature;
11 
12 /**
13  * 完成插件签名:
14  *        告诉MyBatis当前插件用来拦截哪个对象的哪个方法
15  *    @Intercepts(org.apache.ibatis.plugin.Intercepts)和
16  *    签名注解@Signature(org.apache.ibatis.plugin.Signature),这两个注解用来配置拦截器要拦截的接口的方法。
17  *
18  *    @Intercepts注解中的属性是一个@Signature(签名)数组,可以在同一个拦截器中同时拦截不同的接口和方法。
19  *
20  *  @Signature中
21  *      type:设置拦截的接口,可选值是4个:Executor、ParameterHandler、ResultSetHandler、StatementHandler
22  *      method:设置拦截接口中的方法名,需要和接口匹配
23  *      args:设置拦截方法的参数类型数组,通过方法名和参数类型可以确定唯一一个方法
24  */
25 @Intercepts(
26         {
27             @Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
28         })
29 public class MyFirstPlugin implements Interceptor {
30 
31     /**
32      * intercept:拦截: 拦截目标对象的目标方法的执行;
33      */
34     public Object intercept(Invocation invocation) throws Throwable {
35         // TODO Auto-generated method stub
36         System.out.println("MyFirstPlugin...intercept:" + invocation.getMethod());
37         // 执行目标方法
38         Object proceed = invocation.proceed();
39         // 返回执行后的返回值
40         return proceed;
41     }
42 
43     /**
44      * plugin: 包装目标对象的:包装:为目标对象创建一个代理对象
45      */
46     public Object plugin(Object target) {
47         // TODO Auto-generated method stub
48         // 我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
49         System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象" + target);
50         Object wrap = Plugin.wrap(target, this);
51         // 返回为当前target创建的动态代理
52         return wrap;
53     }
54 
55     /**
56      * setProperties:
57      *         将插件注册时 的property属性设置进来
58      */
59     public void setProperties(Properties properties) {
60         // TODO Auto-generated method stub
61         System.out.println("插件配置的信息:" + properties);
62     }
63 
64 }

   3、在mybatis的全局配置文件中配置

1 <!--plugins:注册插件 -->
2 <plugins>
3     <plugin interceptor="com.test.mybatis.plugin.MyFirstPlugin">
4         <property name="username" value="root" />
5         <property name="password" value="123456" />
6     </plugin>
7 </plugins>

   4、编辑测试类TestMybatis.java

 1 public static void main(String[] args) throws IOException {
 2 
 3     // 获取SqlSessionFactory
 4     InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
 5     SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 6 
 7     SqlSession session = sqlSessionFactory.openSession();
 8     try {
 9         EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
10 
11         Employee employee01 = mapper.selectByPrimaryKey(1);
12         System.out.println(employee01.getId());
13 
14     } finally {
15         session.close();
16     }
17 }

  5、运行结果如下:

    

插件开发总结

  插件开发步骤如下

    1、编写插件实现Interceptor接口,并使用@Intercepts注解完成插件签名

1 @Intercepts({                                  @Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
2 })
3 public class MyFirstPlugin implements Interceptor {

    2、在全局配置文件中注册插件

1 <!--plugins:注册插件 -->
2 <plugins>
3     <plugin interceptor="com.test.mybatis.plugin.MyFirstPlugin">
4         <property name="username" value="root" />
5         <property name="password" value="123456" />
6     </plugin>
7 </plugins>

 

  插件的原理

    按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
    多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹;形成代理链
    目标方法执行时依次从外到内执行插件的intercept方法

  插件的作用    

    1、可以统计SQL的执行时间

    2、可以进行分页操作,如插件:Mybatis-PageHelper

    ......

 

posted on 2019-05-28 01:16  H__D  阅读(467)  评论(0编辑  收藏  举报