2.7.解析 plugins 元素 完成 Mybatis 插件的配置
Mybtis的插件机制是一个很强大的功能,它允许我们在Mybatis运行期间切入到Mybatis内部执行我们想要做的一些事情。
mybatis比较火的分页插件page-helper
其实就是基于Mybatis的插件功能实现的。
Mybatis的plugins
DTD定义如下:
<!ELEMENT plugins (plugin+)>
<!ELEMENT plugin (property*)>
<!ATTLIST plugin
interceptor CDATA #REQUIRED
>
plugins
标签下必须定义一个或多个plugin
子节点。
plugin
子节点有一个必填的属性interceptor
,该属性指向一个实现了Interceptor
的类,同时在plugin
标签下面允许出现零个或多个property
标签,用来配置该插件运行时依赖的属性。
比如:
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
Interceptor
是mybatis提供的插件接口定义:
/**
* Mybatis 拦截器(插件)接口定义
*
* @author Clinton Begin
*/
public interface Interceptor {
/**
* 提供被拦截方法的代理实现,完成额外的业务处理
*
* @param invocation 代理
*/
Object intercept(Invocation invocation) throws Throwable;
/**
* 该方法用于处理被拦截对象,返回该对象本身或者为其生成一个代理对象。
* <p>
* 如果返回的是代理对象,我们可以为指定方法实现代理实现:{@link #intercept(Invocation)}
*
* @param target 被拦截的对象
*/
Object plugin(Object target);
/**
* 配置该插件运行时依赖的属性
*/
void setProperties(Properties properties);
}
它定义了三个方法:
intercept
方法用于提供被拦截方法的代理实现,完成额外的业务处理。plugin
方法负责处理被拦截的对象,返回该对象本身或者为其生成一个代理对象。setProperties
方法用于配置该插件运行时依赖的属性。
其中intercept
方法的入参是一个Invocation
类型的对象,Invocation
对象是mybatis提供的一个方法反射操作封装对象,他维护了特定的方法对象及其运行时依赖的参数。
Invocation
定义了三个属性:
/**
* 被代理的对象
*/
private final Object target;
/**
* 被代理的方法
*/
private final Method method;
/**
* 被代理方法的入参
*/
private final Object[] args;
这三个属性的赋值操作是在Invocation
的构造方法中完成的:
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
同时Invocation
不仅对外提供了这三个属性的getter
方法,还提供了一个用于完成方法反射操作的proceed
方法:
/**
* 通过反射完成方法调用
*
* @return 方法返回值
*/
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
补充完基础知识之后,我们回到plugins
元素的解析操作上来:
// 插件配置
pluginElement(root.evalNode("plugins"));
在pluginElement
方法中,XmlConfigBuilder
会依次处理所有的plugin
子节点,获取其interceptor
的属性值,交给BaseBuilder
的resolveClass(String)
方法获取具体的插件实现类,
并读取plugin
子节点下的所有property
属性配置生成Properties
对象。
/**
* 解析plugins节点
*
* @param parent plugins节点
*/
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 处理每一个interceptor
String interceptor = child.getStringAttribute("interceptor");
// 获取运行时属性配置
Properties properties = child.getChildrenAsProperties();
// 获取插件的实现
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 初始化插件配置
interceptorInstance.setProperties(properties);
// 注册插件,在插件职责链中注册一个新的插件实例
configuration.addInterceptor(interceptorInstance);
}
}
}
因为这里用的是
resolveClass
方法来获取插件的实现类,所以对于插件的配置,也是支持别名机制的。
之后通过反射得到具体的插件对象实例,并调用其setProperties
方法配置其运行时需要使用的属性。
完成上诉操作之后,调用configuration
的addInterceptor(Interceptor)
方法完成保存插件的工作。
/**
* 添加一个拦截器
* @param interceptor 拦截器
*/
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
在configuration
的addInterceptor(Interceptor)
方法中将添加插件的工作交给了interceptorChain
的addInterceptor
方法来完成。
interceptorChain
属性在Configuration
中被硬编码为InterceptorChain
类型的实例:
/**
* 插件拦截链(Mybatis插件)
*/
protected final InterceptorChain interceptorChain = new InterceptorChain();
InterceptorChain
定义了一个List<Interceptor>
类型的属性interceptors
用于存放mybatis中所有的插件实现类。
/**
* 所有插件
*/
private final List<Interceptor> interceptors = new ArrayList<>();
对外暴露的addInterceptor
方法就是用来往interceptors
集合中添加Interceptor
实现的:
/**
* 添加一个新的{@link Interceptor#}实例
*/
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
除了addInterceptor
方法之外,InterceptorChain
还对外暴露了getInterceptors
和pluginAll
两个方法。
其中getInterceptors
用于获取当前所有Interceptor
实例:
/**
* 获取所有的Interceptor
*/
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
pluginAll
方法则负责依次调用所有的Interceptor
实例的plugin
方法对目标对象进行处理。
/**
* 调用所有插件的{@link Interceptor#plugin(Object)}方法,包装指定对象
*/
public Object pluginAll(Object target) {
// 多层拦截器对目标对象进行代理,会形成一条职责链
for (Interceptor interceptor : interceptors) {
// 处理被拦截对象,返回该对象本身或者为其生成一个代理对象。
target = interceptor.plugin(target);
}
return target;
}
如果多个Interceptor
对目标对象进行了代理,那么多层代理之间就会形成一条职责链。
职责链模式(责任链模式)也是一种常见的行为型设计模式:
- 责任链模式是由多个对象构成的一条链式结构,该结构中的对象之间通过前者持有后者引用的方式贯穿链路.
- 用户在发起对责任链的请求时,并不知道请求会被责任链中的哪一个对象处理,这样做用户只需要直接对责任链请求即可,而不需要针对每一个具体的对象发起请求.
责任链模式按照处理请求方式的不同可以分为纯责任链和不纯的责任链:
- 纯责任链:
在处理请求过程中,每个节点对于请求的处理方式有且仅有处理和不处理两种,同时请求一定会被责任链中的某一个节点处理,当这个节点处理了请求之后,他一定不会将请求继续往下顺延,即,终止请求在链路中继续运行.
- 不纯的责任链
在不纯的责任链模式内,一个请求可能会被多个节点处理,即每个节点都可能处理请求的一部分,其后续节点同样还有可能处理 请求的部分,甚至,还可能存在没有任何节点处理请求的情形.
理论上讲,由interceptors
形成的代理对象责任链应该是不纯的责任链。