目录
-
- 一、引言
- 二、Interceptor的注册顺序
-
- [2.1 配置文件注册(mybatis-config.xml)](#2.1 配置文件注册(mybatis-config.xml))
- [2.2 代码注册](#2.2 代码注册)
- [2.3 SpringBoot @Order](#2.3 SpringBoot @Order)
- [2.4 扩展 - PageHelper链最后(即最先执行)](#2.4 扩展 - PageHelper链最后(即最先执行))
- 三、plugin机制与InterceptorChain
-
- [3.1 InterceptorChain.pluginAll](#3.1 InterceptorChain.pluginAll)
- [3.2 Interceptor.plugin](#3.2 Interceptor.plugin)
- [3.3 Plugin.wrap(target, this)](#3.3 Plugin.wrap(target, this))
- 四、Configuration调用pluginAll生成Executor
- 五、责任链模式的应用
- 六、执行顺序重点:先注册后执行
- 七、示例代码
- 八、结论
一、引言
在MyBatis中,Interceptor(拦截器)是实现自定义功能的核心扩展点。通过插件机制,开发者可以在SQL执行、参数处理、结果映射等关键流程中插入自定义逻辑。本文将深入介绍MyBatis中Interceptor的执行顺序,重点关注Interceptor的注册顺序、plugin机制、Configuration如何调用pluginAll方法生成Executor,以及责任链模式的应用,并详细说明了"先注册后执行"的机制和Plugin.wrap底层实现。
二、Interceptor的注册顺序
MyBatis的Interceptor通常通过配置文件或代码注册到Configuration对象中。注册顺序非常重要,因为它直接影响拦截器的执行顺序。
2.1 配置文件注册(mybatis-config.xml)
xml
<plugins>
<plugin interceptor="com.example.MyInterceptor1"/>
<plugin interceptor="com.example.MyInterceptor2"/>
</plugins>
拦截器会按照配置文件中出现的顺序依次注册。
2.2 代码注册
java
configuration.addInterceptor(new MyInterceptor1());
configuration.addInterceptor(new MyInterceptor2());
同样,拦截器会按照addInterceptor的调用顺序注册。
2.3 SpringBoot @Order
如果你使用的是 mybatis-spring-boot-starter(推荐方式),可以通过 @Order 注解明确指定顺序:
java
@Bean
@Order(1)
public Interceptor interceptorA() { ... }
@Bean
@Order(2)
public Interceptor interceptorB() { ... }
注册顺序说明:
- Spring 会将所有类型为
Interceptor的 Bean 注入到 MyBatis 的SqlSessionFactory中。 - 默认情况下,Spring 注入集合类型(如 List)时,顺序为 Bean上@Order注解声明的顺序。
2.4 扩展 - PageHelper链最后(即最先执行)
PageHelper通过实现InitializingBean.afterPropertiesSet方法,强制将PageInterceptor放到拦截器列表的最后面,即保证了PageInterceptor会最先被执行,具体代码如下:
java
package com.github.pagehelper.autoconfigure;
import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import java.util.List;
/**
* 自定注入分页插件
*
* @author liuzh
*/
@Configuration
@ConditionalOnBean(SqlSessionFactory.class)
@EnableConfigurationProperties({PageHelperProperties.class, PageHelperStandardProperties.class})
@AutoConfigureAfter(MybatisAutoConfiguration.class)
//@Import(PageHelperProperties.class)
@Lazy(false)
public class PageHelperAutoConfiguration implements InitializingBean {
private final List<SqlSessionFactory> sqlSessionFactoryList;
private final PageHelperProperties properties;
public PageHelperAutoConfiguration(List<SqlSessionFactory> sqlSessionFactoryList, PageHelperStandardProperties standardProperties) {
this.sqlSessionFactoryList = sqlSessionFactoryList;
this.properties = standardProperties.getProperties();
}
@Override
public void afterPropertiesSet() throws Exception {
PageInterceptor interceptor = new PageInterceptor();
interceptor.setProperties(this.properties);
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
if (!containsInterceptor(configuration, interceptor)) {
//========== 重点关注 =============
configuration.addInterceptor(interceptor);
}
}
}
/**
* 是否已经存在相同的拦截器
*
* @param configuration
* @param interceptor
* @return
*/
private boolean containsInterceptor(org.apache.ibatis.session.Configuration configuration, Interceptor interceptor) {
try {
// getInterceptors since 3.2.2
return configuration.getInterceptors().stream().anyMatch(config->interceptor.getClass().isAssignableFrom(config.getClass()));
} catch (Exception e) {
return false;
}
}
}
三、plugin机制与InterceptorChain
3.1 InterceptorChain.pluginAll
MyBatis通过plugin机制实现拦截器的动态包装。核心类是InterceptorChain,它维护一个Interceptor列表,并负责将目标对象(如Executor、StatementHandler等)按顺序包装。
InterceptorChain源码片段
java
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
}
可以看到,pluginAll方法会按照Interceptor注册顺序依次调用每个Interceptor的plugin方法,将目标对象层层包装。
3.2 Interceptor.plugin
Interceptor接口:
java
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {}
}
3.3 Plugin.wrap(target, this)
MyBatis的plugin机制核心在于Plugin.wrap(target, this)方法。其底层实现主要依赖Java的动态代理(JDK Proxy),实现了对目标对象的拦截。
主要流程
-
判断目标对象是否需要拦截
Plugin.wrap会根据Interceptor的@Signature注解,判断目标对象是否实现了需要拦截的接口(如Executor、StatementHandler等)。
-
创建代理对象
如果需要拦截,Plugin.wrap会使用JDK动态代理生成一个代理对象。代理对象会在目标方法被调用时,自动调用Interceptor的intercept方法。
-
责任链传递
多个Interceptor包装后,代理对象会层层嵌套,形成责任链。每个intercept方法内部通常会调用
invocation.proceed(),继续传递到下一个Interceptor或原始对象。
源码简要
java
public class Plugin implements InvocationHandler {
private Object target;
private Interceptor interceptor;
private Map<Class<?>, Set<Method>> signatureMap;
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 判断是否需要拦截
if (shouldIntercept(type, signatureMap)) {
return Proxy.newProxyInstance(
type.getClassLoader(),
getInterfaces(type, signatureMap),
new Plugin(target, interceptor, signatureMap)
);
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 判断是否拦截该方法
if (shouldIntercept(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
}
}
四、Configuration调用pluginAll生成Executor
在MyBatis初始化时,Configuration会创建Executor等核心组件,并通过InterceptorChain的pluginAll方法进行包装:
典型流程(以Executor为例):
java
Executor executor = new SimpleExecutor();
executor = interceptorChain.pluginAll(executor);
这意味着Executor会被所有Interceptor按注册顺序依次包装,形成一条"责任链"。
五、责任链模式的应用
MyBatis的plugin机制本质上是责任链模式(Chain of Responsibility Pattern)的应用。每个Interceptor都可以选择拦截或放行操作,最终形成如下调用链:
- Executor被第一个Interceptor包装,返回一个代理对象。
- 代理对象再被第二个Interceptor包装,依次类推。
- 执行SQL时,调用链会从最外层Interceptor开始,逐层向内传递,直到原始Executor。
这种模式带来的好处是:拦截器可以灵活地插入、组合,且执行顺序完全由注册顺序决定。
六、执行顺序重点:先注册后执行
需要特别强调的是:Interceptor的执行顺序遵循"先注册,后执行"原则。
- 注册时,Configuration会将Interceptor按顺序添加到InterceptorChain的interceptors列表。
- 包装时,pluginAll方法会依次调用每个Interceptor的plugin方法,将目标对象层层包裹。
- 执行时,调用链会从最外层(最后注册的Interceptor)开始,逐层向内传递,直到最先注册的Interceptor,最后到原始对象。
举例说明:
假设注册顺序为:InterceptorA → InterceptorB → InterceptorC
执行包装的顺序(InterceptorChain.pluginAll):InterceptorA.plugin → InterceptorB.plugin → InterceptorC.plugin
最终目标对象的包装顺序为:C包裹B,B包裹A,A包裹原始对象
执行顺序为:C → B → A → 原始对象
七、示例代码
假设有两个拦截器:
java
public class MyInterceptor1 implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyInterceptor1 before");
Object result = invocation.proceed();
System.out.println("MyInterceptor1 after");
return result;
}
}
public class MyInterceptor2 implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyInterceptor2 before");
Object result = invocation.proceed();
System.out.println("MyInterceptor2 after");
return result;
}
}
注册顺序为1→2,执行顺序如下:
MyInterceptor2 before
MyInterceptor1 before
[原始Executor执行]
MyInterceptor1 after
MyInterceptor2 after
八、结论
- Interceptor注册顺序决定执行顺序:先注册的拦截器会被后执行,责任链模式保证了拦截器的顺序性和可扩展性。
- Plugin.wrap(target, this):通过JDK动态代理实现拦截器包装,只有目标对象实现了需要拦截的接口才会被代理,拦截器通过intercept方法实现自定义逻辑。
- 实际开发中,务必关注拦截器的注册顺序,合理设计拦截器的职责和顺序,确保业务逻辑的正确性和可维护性。
如需进一步了解,可以参考MyBatis源码中的InterceptorChain和Plugin类实现,以及项目中的拦截器注册代码。