Mybatis插件配置解析
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
插件的配置通过<plugins>
标签完成,Mybatis文档中给出的例子如下:
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
在XMLConfigBuilder
的配置解析逻辑中,pluginElement(XNode parent)
方法用于解析插件配置。代码如下:
private void parseConfiguration(XNode root) {
...
pluginElement(root.evalNode("plugins"));
...
}
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历plugins标签的所有plugin子标签
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
// 将所有的plugin标签中声明的所有property转化为Properties对象
Properties properties = child.getChildrenAsProperties();
// 实例化配置的plugin
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 将plugin需要的属性初始化到plugin中
interceptorInstance.setProperties(properties);
// 将初始化完成的plugin放入到Configuration对象的interceptorChain属性中
configuration.addInterceptor(interceptorInstance);
}
}
}
可以看到,插件配置被解析时就已经创建了插件的实例对象,而每个插件是按照拦截器模式进行处理业务的,我们这里可以再次考察两个问题:
- 插件的保存顺序如何?
- 拦截器接口到底是怎样的格式?
插件的保存顺序
要考察插件的保存顺序,我们必须深入到如下代码中:
configuration.addInterceptor(interceptorInstance);
考察Configuration
对象的addInterceptor(Interceptor)
方法,可以发现:
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
该方法仅仅为拦截器链添加了一个拦截器罢了,具体的拦截器添加逻辑在InterceptorChain
中,那么拦截器链到底是怎样的结构呢?其实拦截器链就是一个链表:
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
}
其添加逻辑更是简单,完全不用考虑拦截器的执行顺序,仅仅是将链表中添加一个节点罢了。
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
综上所述,我们在Mybatis配置文件中配置的所有插件,在配置解析完毕后,会变成一个拦截器,并存储于Configuration对象的拦截器链中,大致逻辑如下图所示:
【mybatis-config.xml - <plugins>】
| ^
v |
【XMLConfigBuilder.parseConfiguration(parser.evalNode("/configuration")】 // 解析配置文件
| ^
v |
【XMLConfigBuilder.pluginElement(root.evalNode("plugins"))】 // 解析plugins标签
| ^
v |
【XMLConfigBuilder.configuration.addInterceptor(interceptorInstance)】// 创建拦截器,并将拦截器放入到configuration对象的拦截器链中
| ^
v |
【interceptorChain.addInterceptor(interceptor);】
拦截器接口格式
通过上一部分我们知道,Mybatis的插件最后都会被作拦截器处理,这里我们考察一下在Mybatis中拦截器接口的格式:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
该拦截器接口共声明了三个方法:
- setProperties(Properties properties):通过上一节分析我们知道,该方法是在解析plugin标签的最后一步,将plugin标签的子标签中声明的所有properties设置到拦截器实例中。
- plugin(Object target):该方法用于声明通过何种方式实现拦截器模式,Mybatis默认情况下是使用JDK动态代理的方式实现的拦截器模式,你可以覆盖该方法对其进行更改,之后介绍到拦截器具体执行步骤时,会对比拦截器模式这样实现的好处与缺点
- intercept(Invocation):该方法是拦截器执行的真正业务逻辑。
如此看来,Mybatis的拦截器接口声明的三个方法分别用于:初始化、构建以及真正的业务操作。将三者解耦开来,是一种蛮不错设计。
至此,我们已经分析完,Mybatis插件初始化过程,至于更多关于插件执行的细节,我们会在后面进行更深入的分析。不过现在先让我们把Mybatis的配置解析分析完,接下来让我们去看一下最重要的<mappers>
标签的解析。