mybatis拦截器执行过程


title: “mybatis拦截器执行过程”
createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
draft: false
author: “ggball”
tags: [“mybatis”]
categories: [“java”]
description: “mybatis拦截器执行过程”

m[toc]

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

1、使用mybatis的拦截器

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

配置步骤

1.创建拦截器

@Intercepts({
        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
public class SlowSqlInterceptor implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
       // 主要逻辑 拼接参数到sql,并打印
    }

    @Override
    public Object plugin(Object target) {
        // 创建代理对象
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
      	// 设置属性
    }
}
/**
 * @author Clinton Begin
 */
public class Invocation {

  // 目标对象,即ParameterHandler、ResultSetHandler、StatementHandler或者Executor实例
  private final Object target;
  // 目标方法,即拦截的方法
  private final Method method;
  // 目标方法参数
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  public Object getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

  /**
   * 执行目标方法
   * @return 目标方法执行结果
   * @throws InvocationTargetException
   * @throws IllegalAccessException
   */
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

}

拦截器类实现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创建时,会去扫描配置文件的<plugin>标签
  2. 获取<plugin>标签的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

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

添加拦截方法

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

1. mybatis在创建sqlSession时,会创建执行器Executor,同时调用拦截器的pluginAll方法,调用每个拦截器的plugin方法,这个方法主要是创建代理对象,将代理功能增强到被拦截的方法上。
2. 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对象,很多数据都可以直接拿到,避免现拿现查的麻烦。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值