Mybatis源码分析(三)Mybatis插件的使用和源码分析

 

 


先来看下使用:

打印sql语句和执行的时间

1:实现 Interceptor 接口

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class}) })
public class SqlInterceptor implements Interceptor {
  private final Logger logger = LoggerFactory.getLogger(this.getClass());
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
    BoundSql boundSql = statementHandler.getBoundSql();
    String sql = boundSql.getSql();
    logger.info("mybatis intercept sql:{}", sql);
    long t1=System.currentTimeMillis();
    try{
return invocation.proceed(); }catch (Exception e){ logger.error("执行失败"); return null; }finally { long t2=System.currentTimeMillis(); logger.info("执行时间:"+(t2-t1)); } } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { String dialect = properties.getProperty("testProperty"); logger.info("mybatis在配置插件的时候传入的属性:{}", dialect); } }

2:在mybatis的全局配置文件中加上这个插件

 <plugins>
    <plugin interceptor="org.apache.ibatis.demo.SqlInterceptor">
      <property name="testProperty" value="我是测试的"/>
    </plugin>
  </plugins>

看效果:

 


 

原理:

先总结一下:

0: 可以拦截的有四个接口:Executor,ParameterHandler ,ResultSetHandler ,StatementHandler 

1:实现 Interceptor 接口,重写 intercept方法,在注解上面定义拦截的是哪个接口,哪个方法,参数是什么。可以根据拦截的是哪个接口,在方法中getTarget得到哪个实例,然后进行处理。

2:在解析全局配置文件成Configuration对象的时候会加载plugin属性,把自定义插件添加到 InterceptorChain# interceptors集合中。

3:在生成上面四个接口实例的时候,会加载 InterceptorChain# interceptors集合遍历找到符合签名的插件,如果找到了是拦截自己的插件就生成一个代理对象返回出去。

我在定义了, Interceptor接口实现类,在全局配置文件中增加<plugin></plugin>节点,这个节点在mybatis解析全局配置文件成Configuration对象的时候,把接口实现类都添加到了 InterceptorChain 中,这个在上节源码中可以看到。

  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).getDeclaredConstructor().newInstance();
//        设置属性
        interceptorInstance.setProperties(properties);
//        添加到InterceptorChain  中
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

 

 

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

 

 存到 InterceptorChain# interceptors集合中

 

 

mybatis提供了可以在四个接口的实现类的方法执行前进行拦截。

Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters) ResultSetHandler (handleResultSets, handleOutputParameters) StatementHandler (prepare, parameterize, batch, update, query)

 

 

 

 

   补充:  StatementHandler:  在编译sql语句的时候会用到他们的实现类。   ParameterHandler处理sql中参数的填充。

   具体拦截那个接口的那个方法,和上面使用的 @Intercepts 注解里面的方法签名有关,type指定拦截那个接口,method方法名,args是参数。

   具体的效果,就是如果拦截了某个接口,在Configuration创建某个接口实现类的时候,会调用InterceptorChain,如果符合签名的条件,就返回对应实现类的一个动态代理。

public class Configuration {
  protected final InterceptorChain interceptorChain = new InterceptorChain();

  /**对ParameterHandler 进行拦截**/
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  /**对ResultSetHandler 进行拦截**/
  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;
  }

  /**对StatementHandler 进行拦截**/
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

  /**对Executor 进行拦截**/
  public Executor newExecutor(Transaction transaction) {
    return newExecutor(transaction, defaultExecutorType);
  }

  /**对Executor 进行拦截**/
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

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

 

创建的顺序 :1: newExecutor  2:newParameterHandler  3:newResultSetHandler  4:newStatementHandler

以例子中的拦截的 StatementHandler 接口为例子,看下是怎么处理的。

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//    InterceptorChain#pluginAll  传入的参数是原始的 RoutingStatementHandler 对象
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

 

InterceptorChain#pluginAll,遍历处理添加进去的拦截插件 ,目前只有一个自定义的插件。

 

 

 

 

然后走到 自定义的逻辑 SqlInterceptor#plugin中:

 

 

 

Plugin#wrap中

public static Object wrap(Object target, Interceptor interceptor) {
//   得到注解中的签名信息
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
//    看签名信息和当前target 类型是否匹配
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
//    如果匹配的上,返回动态代理,代理逻辑用  new Plugin(target, interceptor, signatureMap) 来封装
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

 

这个逻辑走完,回到newStatementHandler方法中,得到的 statementHandler 已经是个代理对象了。代理逻辑会执行Plugin中的invoke方法,注意代理逻辑中是new Plugin(target,interceptor,singmap)都传递进去了

 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)) {
// 拦截的签名方法匹配 走自定义的拦截逻辑 又传了一个新对象 new Invocation(target, method, args)
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}

 

posted @ 2021-07-24 21:31  蒙恬括  阅读(85)  评论(0编辑  收藏  举报