要讲清楚 MyBatis 插件的加载与生效,最好从两个时间点切入:一是“启动期”的加载,二是“运行时”的织入,因此本章我将通过介绍这两个过程,让大家熟悉 MyBatis 的插件原理。
一、Plugin 加载过程
插件的加载始于 MyBatis 容器的初始化。当 XMLConfigBuilder解析到配置中的 <plugins>标签时,便开始逐步构建插件链。
对于每一个 <plugin>子标签,MyBatis 都会触发对应拦截器的实例化流程:
-
先通过无参构造函数创建
Interceptor对象。 -
再自动调用其
setProperties()方法,以传入配置参数,完成拦截器的个性化设置。
所有初始化完成的拦截器实例,最终被统一注册到 Configuration对象的 interceptorChain中 —— 该成员变量实际上是一个 ArrayList<Interceptor>列表,维护着整个插件链的执行顺序。
加载时序图

源码追踪
-
起点:
SqlSessionFactoryBuilder.build(...)调用解析// 核心代码段 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse());解读:创建
XMLConfigBuilder,随后立即调用parse()方法。所有“启动期”的加载工作都发生在parse()内。 -
解析入口:
XMLConfigBuilder.parse()// 核心代码片段 parseConfiguration(parser.evalNode("/configuration"));解读:它锁定根节点
/configuration并进入逐段解析。 -
关键步骤:进入
parseConfiguration(...)并处理<plugins>// 核心代码片段 pluginsElement(root.evalNode("plugins"));解读:按照固定顺序解析。注意
<plugins>在<settings>之后、<mappers>之前。这个顺序背后有因果:插件需要在后续组件创建前“先被登记”。 -
插件装载:
pluginsElement(...)// 核心代码片段 for (XNode child : context.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); }解读:逐个读取
<plugin>节点,获取interceptor类、构造实例、注入<property>属性,然后将其添加到Configuration中。说明:这一步只做“登记”,不做“织入”。它把插件排进一条链,后续对象创建时会统一查询这条链来决定是否进行包装。
-
注册到配置:
Configuration.addInterceptor(...)// 核心代码片段 public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); }解读:
Configuration不直接维护列表,而是把工作委托给InterceptorChain。说明:把“登记”和“应用”分离到两个阶段、两个类里,利于演化。
Configuration保持“高层入口”,InterceptorChain负责“具体集合与调度”。 -
插件链:
InterceptorChain// 核心代码片段 private final List<Interceptor> interceptors = new ArrayList<>(); public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); }解读:一个简单的列表,按出现顺序保存插件。顺序很关键,因为将来应用插件时是按此顺序包装目标对象。
说明:链式结构让插件具备“组合”的能力,每个插件都能在前一个的包装结果上继续包裹,形成 AOP 风格的层叠。
二、Plugin 织入过程
这个过程主要基于动态代理 + 责任链模式,实现了对核心组件的层层封装,也常被形象地称为 “洋葱模型”。
当框架初始化并创建四大核心组件(例如 Executor)时,包装流程随即启动。在 Configuration的各个 newXXX()方法中,每生成一个核心对象,都会立即调用 interceptorChain.pluginAll(target)方法,给这个对象做一次“全身包装”。
这个过程如同为洋葱逐层裹上外衣——每个插件都可能为目标对象创建一个代理层,最终形成一个由多个代理嵌套而成的调用链。
织入时序图

源码追踪
1. Executor
// 核心代码片段
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : 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);
}
return (Executor) interceptorChain.pluginAll(executor);
}
2. StatementHandler
// 核心代码片段
// org/apache/ibatis/executor/SimpleExecutor.java:45-54
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
// org/apache/ibatis/executor/SimpleExecutor.java:60-69
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
// org/apache/ibatis/session/Configuration.java:724-729
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
return (StatementHandler) interceptorChain.pluginAll(statementHandler);
// org/apache/ibatis/executor/statement/RoutingStatementHandler.java:40-56
switch (ms.getStatementType()) {
case STATEMENT: delegate = new SimpleStatementHandler(...); break;
case PREPARED: delegate = new PreparedStatementHandler(...); break;
case CALLABLE: delegate = new CallableStatementHandler(...); break;
default: throw new ExecutorException(...);
}
// org/apache/ibatis/executor/statement/BaseStatementHandler.java:53-73
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
解读:
-
构建
RoutingStatementHandler时,具体子类继承自BaseStatementHandler。进入父类构造器后立刻创建ParameterHandler与ResultSetHandler,且两者都是通过Configuration各自的pluginAll包裹完成。 -
待具体
StatementHandler子类构造完成,Configuration.newStatementHandler再对整个StatementHandler本体做一次pluginAll。 -
顺序是:
ParameterHandler wrap→ResultSetHandler wrap→StatementHandler wrap
3. ParameterHandler
// 核心代码片段
// org/apache/ibatis/session/Configuration.java:710-715
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
return (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
// 来源调用:org/apache/ibatis/executor/statement/BaseStatementHandler.java:66-73
解读:由 LanguageDriver基于语句语言(XML/注解)创建后,立刻通过 pluginAll包裹,典型拦截点是 setParameters(PreparedStatement)。被 BaseStatementHandler构造器调用并注入到语句处理器内部。
4. ResultSetHandler
// 核心代码片段
// org/apache/ibatis/session/Configuration.java:717-722
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
return (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
// 来源调用:org/apache/ibatis/executor/statement/BaseStatementHandler.java:70-73
解读:直接构造 DefaultResultSetHandler,随后统一 pluginAll包裹,拦截点集中在 handleResultSets(Statement)与 handleCursorResultSets(Statement)。注入时携带已包裹的 parameterHandler,使得前后两个环节的插件增强能够协同。
pluginAll 原理
// 核心代码片段
// org/apache/ibatis/plugin/InterceptorChain.java:29-33
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
// org/apache/ibatis/plugin/Interceptor.java:20-24
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// org/apache/ibatis/plugin/Plugin.java:43-51
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
}
return target;
// org/apache/ibatis/plugin/Plugin.java:53-64
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
// org/apache/ibatis/plugin/Invocation.java:60-63
public Object proceed() {
return method.invoke(target, args);
}
解读:
-
pluginAll会逐个调用拦截器的plugin方法。默认实现通过Plugin.wrap检查该拦截器的@Intercepts/@Signature是否命中目标对象所实现的接口;命中则生成“只实现必要接口”的 JDK 动态代理。 -
进入代理后,根据签名匹配决定是否拦截;命中则执行
interceptor.intercept(Invocation),调用Invocation.proceed()继续执行原方法。
总结
总的来说,MyBatis 插件的核心原理可概括为 “动态代理”与“责任链”的巧妙结合。
-
启动时,所有声明的插件(
Interceptor)都会被注册到一个统一的拦截器链中。 -
创建核心对象时,会通过
Configuration的pluginAll()方法,基于动态代理技术,按声明顺序依次将每个插件“包裹”在目标对象外部,形成类似“洋葱模型”的链式结构。 -
运行时,方法调用会从最外层代理逐层向内传递。在调用链的每一个环节,插件都可以通过
@Signature匹配并拦截特定方法,执行自定义逻辑(如修改 SQL、记录日志等),再通过invocation.proceed()将调用传递给链中的下一个代理或最终的真实对象。
这种设计使得插件能够以无侵入的方式,灵活地切入 MyBatis 执行流程的各个环节。
904

被折叠的 条评论
为什么被折叠?



