Mybatis拦截器源码深度解析

目录:

一. 建立拦截器链
1. 创建对象
2. 建立配置文件
3. 加载拦截器链
二. 方法调用解析
1. 对请求对象进行拦截器包装
2. 执行调用
三. 小结

Mybatis拦截器 可以帮助我们在执行sql语句过程中增加插件以实现一些通用的逻辑,比如对查询sql分页、数据权限处理等。

允许使用插件拦截的方法调用包括:

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

各方法的具体作用可以通过 Mybatis之sqlSession调用链分析 进行了解。

方法调用时加载拦截器链的总体时序图如下:

一. 建立拦截器链

1. 创建对象

建立拦截器对象,对Executor接口的实现类上的update方法调用进行拦截:

// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  public void setProperties(Properties properties) {
  }
}

Mybaits中通过注解Intercepts来设定当前拦截器是否对被拦截的请求进行处理,例子中的注解指定对Executor的所有update方法进行拦截处理。

  • Intercepts
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
  Signature[] value();
}
  • Signature
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
  Class<?> type();

  String method();

  Class<?>[] args();
}

对注解的解析在Plugin类中。

2. 建立配置文件

可以通过XMl和注解两种方式进行配置,通过解析配置建立拦截器链。

  • xml配置:
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>
  • 注解配置:
@Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

3. 加载拦截器链

以XML配置好拦截器好,从XML解析源码中,可以看到通过xml标签属性 interceptor 把拦截器加入 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).newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

二. 方法调用解析

之前我们配置了一个对Executor的update方法进行拦截的插件,那么看下具体的执行过程。在调用Configuration生成执行对象时,通过拦截器链对对象进行包装

  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进行包装,因为使用了JDK的动态代理,所以返回对象必须为接口
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

1. 对请求对象进行拦截器包装

通过InterceptorChain的pluginAll方法,对interceptors集合循环,依次对target(也就是上面传入的executor对象)进行代理:

public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      // target通过拦截器链,循环迭代代理
      target = interceptor.plugin(target);
    }
    return target;
  }

自定义的拦截器需要实现Interceptor实现的plugin方法,该方法用来给target增加代理,推荐直接调用Plugin.wrap(target, this)方法(这里将每一个代理对象的创建放在Plugin类中的静态方法,但是每新增一个插件都需要写这个方法)

  @Override
  public Object plugin(Object target) {
    // 注意第二个参数 为this,通过回调自己,将参数传递给Plugin对象,plugin代理对象执行时,如果符合条件,将回调target的intercept,参见本文2小节
    return Plugin.wrap(target, this);
  }

Plugin实现了InvocationHandler接口,其类图如下:

Plugin的wrap方法用来创建target的代理对象:

// 参数interceptor为 ExamplePlugin类对象
public static Object wrap(Object target, Interceptor interceptor) {
    // 1. 获取注解中的配置,class对应Signature中的type属性,Set<Method>对应Signature中的method属性
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    // 2. 获取被代理对象的class类对象,这里为Executor的接口实现类
    Class<?> type = target.getClass();
    // 3. 获取符合对象target接口的拦截器(这里为Executor.class)
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    // 如果注解包含目标对象接口,对Plugin对象进行代理,返回的Plugin代理对象,调用目标方法时会进行Pluing的invoke()方法
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          // 代理对象传入参数包括 目标类、该拦截器对象、注解配置解析Map
          new Plugin(target, interceptor, signatureMap));
    }
    // 如果该插件注解中TYPE值不包含目标对象接口,则不处理,直接返回目标对象
    return target;
  }

其中getAllInterFaces方法用来判断判断注解中是否包含目标对象接口:

private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<>();
    while (type != null) {
      // 如果传入Executor的实现类,那么这里为Executor接口
      for (Class<?> c : type.getInterfaces()) {
        // 1. 判断注解中是否包含目标对象接口
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      // 2. 如果继承父接口,继续循环判断
      type = type.getSuperclass();
    }
    // 返回该插件注解与被代理对象匹配的所有的接口类对象数组
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }

2. 执行调用

当调用被代理对象Execute的所有方法,都会进入Plugin的invoke方法:

  // 代理类只用来做流程判断,不增加具体的业务逻辑,业务逻辑统一在实现Invocation接口的插件类中增加
  // 对该方法进行递归调用(包括所有已加载进拦截器链中的拦截器,首先判断当前拦截器中的注解条件是否满足,满足的话执行当前拦截器,否则调用target的方法,进入下一个拦截器)
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      // 判断Signature参数method是否包含当前调用方法,如果包含,进入拦截器intercept方法;否则,跳过该拦截器继续运行
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

自定义拦截器中intercept实现如下:

public Object intercept(Invocation invocation) throws Throwable {

    // TODO 增加自己的业务需求

    return invocation.proceed();
  }

Invocation相当于责任链中的请求类,其封装反射参数,包含target(target通过InterceptorChain的pluginAll循环代理,因此可以是拦截器的代理类,当经过最后一个拦截器时,为实际调用对象)、method、args,该类主要用来减少intercept方法调用时传入的参数数量

其中proceed方法如下:

public Object proceed() throws InvocationTargetException, IllegalAccessException {
    // 通过反射执行target的方法
    // 如果target依旧为Plugin代理类,则继续进行代理类Plugin中的invoke方法中
    return method.invoke(target, args);
  }

三. 小结

Mybatis拦截器主要实现以下几个点:

  • 通过注解判断被拦截的请求是否符合当前的拦截器。
  • 支持横向扩展,可以自定义拦截器并加入到拦截器链中。
  • 支持请求在拦截器链中依次传递(Invocation类)。

    其结合责任链模式使请求和处理解耦,但是每一次请求都要通过责任链上的所有拦截器,也就是一次调用需要所有拦截器进行判断,因此也有一些局限性。

相关文章:

一文读懂JDBC

mybaits动态代理之最小demo实现

Mybatis之sqlSession调用链分析

了解更多请关注微信公众号:

原文地址:https://www.cnblogs.com/ryanLikeCode/p/10084449.html

时间: 2024-11-07 23:28:24

Mybatis拦截器源码深度解析的相关文章

JAVA框架底层源码剖析系列Spring,Mybatis,Springboot,Netty源码深度解析

<Spring源码深度解析>从核心实现和企业应用两个方面,由浅入深.由易到难地对Spring源码展开了系统的讲解,包括Spring的设计理念和整体架构.容器的基本实现.默认标签的解析.自定义标签的解析.bean的加载.容器的功能扩展.AOP.数据库连接JDBC.整合MyBatis.事务.SpringMVC.远程服务.Spring消息服务等内容. <Spring源码深度解析>不仅介绍了使用Spring框架开发项目必须掌握的核心概念,还指导读者如何使用Spring框架编写企业级应用,并

源码深度解析SpringMvc请求运行机制(转)

源码深度解析SpringMvc请求运行机制 本文依赖的是springmvc4.0.5.RELEASE,通过源码深度解析了解springMvc的请求运行机制.通过源码我们可以知道从客户端发送一个URL请求给springMvc开始,到返回数据给客户端期间是怎么运转的. 1.用户请求处理过程: 1.用户发送请求时会先从DispathcherServler的doService方法开始,在该方法中会将ApplicationContext.localeResolver.themeResolver等对象添加到

spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理

@Configuration注解提供了全新的bean创建方式.最初spring通过xml配置文件初始化bean并完成依赖注入工作.从spring3.0开始,在spring framework模块中提供了这个注解,搭配@Bean等注解,可以完全不依赖xml配置,在运行时完成bean的创建和初始化工作.例如: public interface IBean { } public class AppBean implements IBean{ } //@Configuration申明了AppConfig

Spring源码深度解析pdf

下载地址:网盘下载 <Spring源码深度解析>从核心实现和企业应用两个方面,由浅入深.由易到难地对Spring源码展开了系统的讲解,包括Spring的设计理念和整体架构.容器的基本实现.默认标签的解析.自定义标签的解析.bean的加载.容器的功能扩展.AOP.数据库连接JDBC.整合MyBatis.事务.SpringMVC.远程服务.Spring消息服务等内容. <Spring源码深度解析>不仅介绍了使用Spring框架开发项目必须掌握的核心概念,还指导读者如何使用Spring框

Spring源码深度解析第一天

其实第一天已经过去了,今天是第二天.iteye刚注册的小号就被封了.不论是它的失误还是他的失误总之我跟iteye是没有缘分了. 昨天基本没有进展.所以从今天开始说了.下面流水账开始了. <Spring源码深度解析>这本书没有pdf完整版是让我很失望的.如果有完整版即使看完了我也会选择买一本实体如果有用的话. 书中说从github下载源码.发现github没有想象中的简单易懂.还需要记忆很多命令才能玩得转.从github上获得了Spring源码后需要使用Gradle来编译成eclipse项目.g

SPRING技术内幕,Spring源码深度解析

 SPRING技术内幕,Spring源码深度解析 SPRING技术内幕:深入解析SPRING架构与设计原理(第2版)[带书签].pdf: http://www.t00y.com/file/78131650 Spring源码深度解析 [郝佳编著] sample.pdf: http://www.t00y.com/file/78131634 [jingshuishenliu.400gb.com]Spring Data.pdf: http://www.t00y.com/file/78256084 [

spring源码深度解析— IOC 之 开启 bean 的加载

概述 前面我们已经分析了spring对于xml配置文件的解析,将分析的信息组装成 BeanDefinition,并将其保存注册到相应的 BeanDefinitionRegistry 中.至此,Spring IOC 的初始化工作完成.接下来我们将对bean的加载进行探索. 之前系列文章: spring源码深度解析— IOC 之 容器的基本实现 spring源码深度解析— IOC 之 默认标签解析(上) spring源码深度解析— IOC 之 默认标签解析(下) spring源码深度解析— IOC

源码深度解析SpringMvc请求运行机制

本文依赖的是springmvc4.0.5.RELEASE,通过源码深度解析了解springMvc的请求运行机制.通过源码我们可以知道从客户端发送一个URL请求给springMvc开始,到返回数据给客户端期间是怎么运转的. 1.用户请求处理过程: 1.用户发送请求时会先从DispathcherServler的doService方法开始,在该方法中会将ApplicationContext.localeResolver.themeResolver等对象添加到request中,紧接着就是调用doDisp

iOS开发之Masonry框架源码深度解析

Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁.Masonry简化了NSLayoutConstraint的使用方式,让我们可以以链式的方式为我们的控件指定约束.本篇博客的主题不是教你如何去使用Masonry框架的,而是对Masonry框架的源码进行解析,让你明白Masonry是如何对NSLayoutConstraint进行封装的,以及Masonry框架中的各个部分所扮演的角色是什么样的.在Masonry框架中,仔细