欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Mybatis源码学习第十一课---插件开发原理

程序员文章站 2024-01-02 23:44:58
...

一.插件开发原理

插件是用来改变或者扩展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.运行测试用例

二.插件开发实例

定义一个阈值,当查询操作运行时间超过这个阈值,记录日志供运维人员定位慢查询。下面是在日志debug模式下,如果不是debug等级下 不需要取Statement代理对象

  • 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语句
  <E> List<E> 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前面。
    <plugins>
        <plugin interceptor="com.enjoylearning.mybatis.Interceptors.ThresholdInterceptor"> 
            <property name="threshold" value="10"/>
        </plugin>
    </plugins>
  • 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语句:“aaa@qq.com: 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, aaa@qq.com, note=lison的备注, positionId=]

3.责任链模式

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

Mybatis源码学习第十一课---插件开发原理

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

责任链模式的优点:

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

4.mybatis插件模块源码分析

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

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
     //解析<properties>节点
      propertiesElement(root.evalNode("properties"));
      //解析<settings>节点
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //解析<typeAliases>节点
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析<plugins>节点
      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<Interceptor> interceptors = new ArrayList<>();
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

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

 

    <plugins>

        <plugin interceptor="com.enjoylearning.mybatis.Interceptors.ThresholdInterceptor">
            <property name="threshold" value="10"/>
        </plugin>

        <!-- <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="pageSizeZero" value="true" />
        </plugin> -->

    </plugins>

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);
  }

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

 

Mybatis源码学习第十一课---插件开发原理

 

Mybatis源码学习第十一课---插件开发原理

 

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<Class<?>, Set<Method>> 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 <E> List<E> 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.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

query的时候,进入invoke调用:

  @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)) {//如果当前方法需要被拦截,则调用interceptor.intercept方法进行拦截处理
        return interceptor.intercept(new Invocation(target, method, args));
      }
      //如果当前方法不需要被拦截,则调用对象自身的方法
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

调用是各个变量的值:

Mybatis源码学习第十一课---插件开发原理

这里调用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;

参考

相关标签: 源码分析

上一篇:

下一篇: