mybatis核心层源码分析4-插件开发原理

1.插件开发原理

插件是用来改变或者扩展mybatis的原有功能,mybatis的插件就是通过继承Interceptor拦截器实现的,在没有完全理解插件之前禁止使用插件对mybatis进行扩展,否则可能会导致严重问题。

mybatis中能使用插件进行拦截的接口和方法如下:

  • Executor(update query flushStatement commit rollback getTransaction close isClose)
  • StatementHandler(prepare parameterize batch update query)
  • ParameterHandler (getParameterObject setParameters)
  • ResultHandler(handlerResults handlerCursorResultSets handleOutputParameters)

插件开发的步骤:

  • step1.实现Interceptor接口方法
  • step2.确定拦截的签名
  • step3.在配置文件中配置插件
  • step4.运行测试用例

2.插件开发实例

定义一个阈值,当查询操作运行时间超过这个阈值,记录日志供运维人员定位慢查询。

  • step1.实现Interceptor接口方法
@Intercepts({
        @Signature(type=StatementHandler.class,method="query",args={Statement.class, ResultHandler.class})
//  @Signature(type=StatementHandler.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
})

public class ThresholdInterceptor implements Interceptor {

    private long threshold;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long begin = System.currentTimeMillis();
        Object ret = invocation.proceed();
        long end=System.currentTimeMillis();
        long runTime = end - begin;
        if(runTime>=threshold){
            Object[] args = invocation.getArgs();
            Statement stat = (Statement) args[0];
            MetaObject metaObjectStat = SystemMetaObject.forObject(stat);
            PreparedStatementLogger statementLogger = (PreparedStatementLogger)metaObjectStat.getValue("h");
            Statement statement = statementLogger.getPreparedStatement();
            System.out.println("sql语句:“"+statement.toString()+"”执行时间为:"+runTime+"毫秒,已经超过阈值!");
        }
        return ret;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.threshold = Long.valueOf(properties.getProperty("threshold"));
    }

}

特别说明:PreparedStatementLogger实现了InvocationHandler接口,是代理对象中的h,也是让类具有日志打印功能的代理增强类。

PreparedStatementLogger statementLogger = (PreparedStatementLogger)metaObjectStat.getValue("h");
public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler {
  • step2.确定拦截的签名
    拦截StatementHandler的query语句。不拦截Executor,是因为Executor在调用StatementHandler的query前后,还做了很多其他事情。
public interface StatementHandler {
//执行select语句
   List query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
@Intercepts({
        @Signature(type=StatementHandler.class,method="query",args={Statement.class, ResultHandler.class})
//  @Signature(type=StatementHandler.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
})
  • step3.在配置文件中配置插件
    plugins放到配置文件mybatis-config.xml最后报异常了,最后放在environment前面。
    
         
            
        
    
  • step4.运行测试用例
    @Test
    // 测试自动映射以及下划线自动转化驼峰
    public void quickStart() throws IOException {
        // 2.获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 3.获取对应mapper
        TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
        // 4.执行查询语句并返回结果
        TUser user = mapper.selectByPrimaryKey(1);
        System.out.println(user.toString());

    }

结果:

sql语句:“com.mysql.jdbc.JDBC4PreparedStatement@7334aada: select
         
        id, user_name, real_name, sex, mobile, email, note,
        position_id
     
        from t_user
        where id = 1”执行时间为:15毫秒,已经超过阈值!
TUser [id=1, userName=lison, realName=李小宇, sex=1, mobile=186995587422, [email protected], note=lison的备注, positionId=]

3.责任链模式

责任链模式把一件工作分别经过链上的各个节点,让这些节点依次处理这个工作,和装饰器模式不同,每个节点都知道后继者是谁,适合为完成同一个请求需要多个处理类的场景。


  • Handler:定义了一个处理请求的标准接口;
    ConcreteHandler:具体的处理者,处理它负责的部分,根据业务可以结束处理流程,也可以将请求转发给它的后继者;
    client:发送者,发起请求的客户端。

责任链模式的优点:

  • 降低耦合度。它将请求的发送者和接收者解耦。
  • 简化了对象,使得对象不需要知道链的结构。
  • 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,运行动态地新增或删除责任。
  • 增加新的请求处理类很方便。

4.mybatis插件模块源码分析

4.1 插件的初始化(XMLConfigBuilder.pluginElement)

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
     //解析节点
      propertiesElement(root.evalNode("properties"));
      //解析节点
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //解析节点
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析节点
      pluginElement(root.evalNode("plugins"));
  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      //遍历所有的插件配置
      for (XNode child : parent.getChildren()) {
        //获取插件的类名
        String interceptor = child.getStringAttribute("interceptor");
        //获取插件的配置
        Properties properties = child.getChildrenAsProperties();
        //实例化插件对象
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        //设置插件属性
        interceptorInstance.setProperties(properties);
        //将插件添加到configuration对象,底层使用list保存所有的插件并记录顺序
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
public class Configuration {
  /*插件集合*/
  protected final InterceptorChain interceptorChain = new InterceptorChain();

  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }
public class InterceptorChain {

  private final List interceptors = new ArrayList<>();
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

注意:责任链的顺序是以链表的顺序为准的,链表的顺序又是以配置顺序为准的,因此,责任链中每一个处理节点不需要知道下一个处理节点是谁,达到解耦的目的。

    

        
            
        

        

    

4.2 插件的加载(Configuration.new*方法,四大对象的创建)

  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //创建RoutingStatementHandler对象,实际由statmentType来指定真实的StatementHandler来实现
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

  public Executor newExecutor(Transaction transaction) {
    return newExecutor(transaction, defaultExecutorType);
  }

调试可知加载的时机如下:



statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

调用ThresholdInterceptor.plugin():

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
  //静态方法,用于帮助Interceptor生成动态代理
  public static Object wrap(Object target, Interceptor interceptor) {
    //解析Interceptor上@Intercepts注解得到的signature信息
    Map, Set> signatureMap = getSignatureMap(interceptor);
    Class type = target.getClass();//获取目标对象的类型
    Class[] interfaces = getAllInterfaces(type, signatureMap);//获取目标对象实现的接口(拦截器可以拦截4大对象实现的接口)
    if (interfaces.length > 0) {
      //使用jdk的方式创建动态代理
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

4.3 插件的调用(Plugin.wrap、Plugin.invoke)

  @Override
  //查询的实现
  public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();//获取configuration对象
      //创建StatementHandler对象,
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
      stmt = prepareStatement(handler, ms.getStatementLog());
      //通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

query的时候,进入invoke调用:

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //获取当前接口可以被拦截的方法
      Set methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {//如果当前方法需要被拦截,则调用interceptor.intercept方法进行拦截处理
        return interceptor.intercept(new Invocation(target, method, args));
      }
      //如果当前方法不需要被拦截,则调用对象自身的方法
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

调用是各个变量的值:


这里调用ThresholdInterceptor的intercept:

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long begin = System.currentTimeMillis();
        Object ret = invocation.proceed();
        long end=System.currentTimeMillis();

其中proceed为:

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

5.Inteceptor调用原理——核心就是递归思想

interceptorChain.pluginAll 方法:

public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
    }
    return target;
}

前面我们配置拦截器的顺序是1,2,3。在这里也会按照 1,2,3 的顺序被层层代理,代理后的结构如下:

Interceptor3:{
    Interceptor2: {
        Interceptor1: {
            target: Executor
        }
    }
}

整体的调用逻辑:

Interceptor3 前置处理
Interceptor2 前置处理
Object result = executor.query(6个参数方法);     
Interceptor2 后续处理  
Interceptor3 后续处理   
return result;

参考

  • 1)享学课堂Lison老师笔记
  • 2)PageHelper的Interceptor原理说明

你可能感兴趣的:(mybatis核心层源码分析4-插件开发原理)