m[toc]

mybatis的拦截器本人平时也很少用到,没了解之前,也只是知道运用到了动态代理用来增强方法的功能,但是不了解其中的原理。为了更好的使用mybatis,这次,我记录下我所了解的mybatis的原理,本文不一定完全正确,可能有理解不到位的地方。

1、使用mybatis的拦截器

像平常使用mybatis框架时,如果哪句sql报错了,我们可以通过控制台或日志打印的sql去查看sql的问题,但是如果sql有太多的参数,其实是很不方便的,自己还得手动去把一个一个参数给设置上,有些浪费时间,这时候就可以利用mybatis的拦截器去帮我们把参数给设置上。

配置步骤

1.创建拦截器

 1@Intercepts({
 2        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
 3createTime: 2021-12-08T12:19:57+08:00
 4updateTime: 2021-12-08T12:19:57+08:00
 5        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
 6})
 7public class SlowSqlInterceptor implements Interceptor {
 8
 9
10    @Override
11    public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
12       // 主要逻辑 拼接参数到sql,并打印
13    }
14
15    @Override
16    public Object plugin(Object target) {
17        // 创建代理对象
18        return Plugin.wrap(target, this);
19    }
20
21    @Override
22    public void setProperties(Properties properties) {
23      	// 设置属性
24    }
25}
 1/**
 2 * @author Clinton Begin
 3 */
 4public class Invocation {
 5
 6  // 目标对象,即ParameterHandler、ResultSetHandler、StatementHandler或者Executor实例
 7  private final Object target;
 8  // 目标方法,即拦截的方法
 9  private final Method method;
10  // 目标方法参数
11  private final Object[] args;
12
13  public Invocation(Object target, Method method, Object[] args) {
14    this.target = target;
15    this.method = method;
16    this.args = args;
17  }
18
19  public Object getTarget() {
20    return target;
21  }
22
23  public Method getMethod() {
24    return method;
25  }
26
27  public Object[] getArgs() {
28    return args;
29  }
30
31  /**
32   * 执行目标方法
33   * @return 目标方法执行结果
34   * @throws InvocationTargetException
35   * @throws IllegalAccessException
36   */
37  public Object proceed() throws InvocationTargetException, IllegalAccessException {
38    return method.invoke(target, args);
39  }
40
41}

拦截器类实现intercepter接口

  • intercepter方法主要写拦截方法的逻辑,Invocation对象主要有三个内置对象和proceed方法,proceed方法的作用就是用来执行代理对象的方法,对象target是被代理的对象实例,对象method是拦截方法,对象args是被调用方法传入的参数,很符合调用代理对象invoke方法的条件。

  • plugin方法接受实际对象,作用返回一个代理对象,这里是调用了Plugin提供的warp方法,方便创建代理对象,==我们也可以自己写创建代理对象的代码==。

  • setProperties方法设置属性,当拦截器被扫描到时,会调用此方法。
    createTime: 2021-12-08T12:19:57+08:00
    updateTime: 2021-12-08T12:19:57+08:00

2.mybatis配置文件配置插件

image-20211204171440931

2、mybatis的拦截器如何创建的

image-20211206122358988

image-20211206122417844

  1. configuration创建时,会去扫描配置文件的标签
  2. 获取标签的interceptor属性
  3. 获取拦截器属性,转换为Properties对象
  4. 创建拦截器实例 利用TypeAliasRegistry的resolveAlias方法,将传进来的别名,判断如果别名在TYPE_ALIASES里面,则直接获取类对象,如果不是则反射获取类对象
  5. 设置拦截器实例属性信息 将第三步的properties属性添加到拦截器实例里面
  6. 將拦截器实例添加到拦截器链中 (拦截器在configuration里面)

3、mybatis的拦截器在哪些时机会被使用到

在Configuration类的

newParameterHandler()、newResultSetHandler()、newStatementHandler()、newExecutor()

这些工厂方法中,都调用了InterceptorChain对象的pluginAll()方法。-

image-20211206140008644

image-20211206140028014

image-20211206140042350

image-20211206135945837

image-20211206100432298

 1  /**
 2   * 该方法用于创建Executor、ParameterHandler、ResultSetHandler、StatementHandler的代理对象
 3   Plugin.warp()方法首先获取自定义的拦截类上的@Signature注解上的信息并存入map,那就知道了要拦截哪些对象地哪些方法,然后判断传入的target对象是否满足拦截对象的类型,满足则创建代理对象,不满足则直接返回原对象。
 4   * @param target
 5   * @param interceptor
 6   * @return
 7   */
 8  public static Object wrap(Object target, Interceptor interceptor) {
 9    // 调用getSignatureMap()方法获取自定义插件中,通过Intercepts注解指定的方法
10    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
11    Class<?> type = target.getClass();
12    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
13    if (interfaces.length > 0) {
14      return Proxy.newProxyInstance(
15          type.getClassLoader(),
16          interfaces,
17          new Plugin(target, interceptor, signatureMap));
18    }
19    return target;
20  }

添加拦截方法

这里拿在执行器上添加拦截方法举例:

11. mybatis在创建sqlSession时,会创建执行器Executor,同时调用拦截器的pluginAll方法,调用每个拦截器的plugin方法,这个方法主要是创建代理对象,将代理功能增强到被拦截的方法上。
22. Plugin.warp()方法首先获取自定义的拦截类上的@Signature注解上的信息并存入map,那就知道了要拦截哪些对象地哪些方法,然后判断传入的target对象是否满足拦截对象的类型,满足则创建代理对象,不满足则直接返回原对象。

拦截器被使用到的过程

image-20211206155858474

image-20211206160023347

image-20211206160054776

image-20211206160144051

image-20211206160202063

image-20211206160249200

image-20211206160309014

image-20211206160510749

createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
3. configuration创建statementHandler代理对象,同样有关于statementHandler的拦截器,也会创建代理类
4. statementHandler执行query方法,如果statementHandler对象是代理对象,则进入Plugin的invoke方法,如果当前执行的方法符合被拦截的方法的要求,那么就会执行拦截方法,否则不执行拦截方法,如果statementHandler对象不是代理对象,直接执行原方法。

4、总结

原来以为拦截器只是简单的使用下动态代理,看了mybatis的拦截器发现,一个经得起捶打的功能是不可能那么简单的,里面用到了动态代理解决mapper的实现问题,适配器模式用来解决结果对象,参数对象的映射,而且在我看来configuration类做了太多的工作,很多初始化的数据都能在里面找到,只要持有configuration对象,很多数据都可以直接拿到,避免现拿现查的麻烦。

器模式用来解决结果对象,参数对象的映射,而且在我看来configuration类做了太多的工作,很多初始化的数据都能在里面找到,只要持有configuration对象,很多数据都可以直接拿到,避免现拿现查的麻烦。