Mybatis插件
一、自定义插件
1.1 示例
- 如下是Mybatis分页插件里面实现的一个插件,我们从这里开始看实现自定义插件的方法
@Intercepts(
{
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
public class QueryInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
if(args.length == 4){
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
1.2 要点
- 要点1:通过注解给出拦截器需要拦截类型和需要拦截的方法,默认四种接口类型包括Executor、StatementHandler、ParameterHandler和ResultSetHandler自定义拦截器必须使用Mybatis提供的注解来指明我们要拦截的是四类中的哪一个类接口。
Signature指明拦截器需要拦截哪一个接口的哪一个方法,内部type对应四类接口中的某一个(比如是 Executor),
method对应接口中的方法 , args对应方法参数(重载)
Interceptor接口的三个方法中, intercept实现插件的逻辑, plugin返回一个插件代理对象(插件的实现是基于动态代理的),setProperties是属性赋值方法。
对于plugin方法因为实现起来比较复杂,实际上Mybatis已经为我们提供了一种实现,只需要简单调用Plugin.wrap(target, this)方法即可,如此就实现了一个插件。
不同类型拦截器顺序:Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler
同一个类型拦截器顺序: 根据Mybatis核心配置文件配置的位置,从上往下
二、插件的配置和解析
2.1 配置
- 插件的使用如下,只需要在核心配置文件中配置插件即可
<!--配置分页插件-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- config params as the following -->
<!--<property name="param1" value="value1"/>-->
</plugin>
</plugins>
2.2 解析
2.2.1 XMLConfigBuilder#parseConfiguration
- Mybatis的插件在核心配置文件中配置,而核心配置文件的解析是主流程的第一阶段,可以参考16-Mybatis 核心流程01-初始化阶段。我们直接从主配置文件的解析方法:XMLConfigBuilder#parseConfiguration开始看,下面省去了其他的代码,从中可以看到解析插件的入口方法是XMLConfigBuilder#pluginElement
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
pluginElement(root.evalNode("plugins"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
2.2.2 XMLConfigBuilder#pluginElement
- XMLConfigBuilder#pluginElement中完成插件的对象的实例化,赋值,并保存到Configuration对象里面的插件链中
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);
}
}
}
protected final InterceptorChain interceptorChain = new InterceptorChain();
- 由2.2这里面的2段代码我们看到,在配置解析阶段就会把插件都放到Configuration对象的interceptorChain属性中去,由此解析完成,后面就是代理阶段来生成相关的类实现功能增强。
三、代理
3.1 代理增强
- 前面提到过,插件是基于动态代理来实现的,假设我有一个插件需要对StatementHandler进行一定的处理,那么就会生成一个代理对象,这个代理对象在原有StatementHandler的功能基础上,添加了我这个插件想要做的工作。这个生成代理的过程实际上是在四种类型的对象初始化的时候做的,我们看下面源码(Configuration.java)
3.1.1 Configuration
- Configuration在配置解析时获取到了全部插件对象实例,在创建四大对象的时候,这些插件实例会排上用场。
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
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 = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
3.1.2 InterceptorChain
- 在前面的3.1.1中获取代理对象调用的是InterceptorChain#pluginAll,我们来看看这个里面的细节。
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
}
- 由此我们看到,在初始化的时候会调用interceptorChain.pluginAll(object) 来获取增强后的object(Object属于四类中的某一类)。
3.2 Plugin类
3.2.1 细节
- 上面我们看到了初始化过程的代理增强对象的获取,不过我们没有看增强对象的生成细节,怎么增强的?我们知道插件的逻辑是在intercept中实现的,在1.2的要点2我们提到,plugin方法是用于返回增强的代理对象,那么我们先看看已有的插件这里是如何实现的。在1.1的分页插件中plugin方法是调用Plugin.wrap(target, this),在Mybatis源码工程中的插件测试代码ExamplePlugin中也是 return Plugin.wrap(target, this);如下所示:
@Intercepts({})
public class ExamplePlugin implements Interceptor {
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
}
3.2.2 Plugin类
- Plugin类使用了动态代理模式,它实现了InvocationHandler接口,内部持有Interceptor插件对象和目标对象,实际程序运行时期的对象就是一个Plugin实例,它会判断什么时候该执行插件的intercept方法,带注释源码如下:
public class Plugin implements InvocationHandler {
private Object target;
private Interceptor interceptor;
private Map<Class<?>, Set<Method>> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
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);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet<Method>();
signatureMap.put(sig.type(), methods);
}
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<Class<?>>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
}
3.3 小结
A.首先是配置解析。解析完毕之后所有的插件都会被实例化,保存到Configuration对象的interceptorChain属性里面(XMLConfigBuilder#pluginElement)
B.第二是初始化。四大对象的初始化都在Configuration中,初始化的时候会通过interceptorChain将四大对象进行增强(Configuration四大对象构造方法)
B1 这个增强获得的代理对象是Plugin类的实例,该实例内部持有四大对象和插件对象实例。(Plugin#wrap)
B2 增强对象初始化的时候就会把需要拦截的方法保存好,便于后面真正调用方法的时候判断(Plugin#wrap第一行代码)
B3 增强对象调用方法的时候会判断这个方法是否需要拦截,是就走插件对象的intercept方法,不需要就直接调用四大对象自己的方法(Plugin#invoke)
B4 增强对象很可能是一个多层的代理包装,每一个插件会代理一次(InterceptorChain#pluginAll)
B5 对B1的过程,返回代理对象是在Interceptor#plugin中需要用户自行完成的事情,需要根据插件注解等信息实现返回代理对象的逻辑,贴心的Mybatis已经在(Plugin#wrap )中实现好了,直接使用即可。(从入参Plugin.wrap(target, this)也可以看出,代理对象是持有目标对象和插件对象实例的,印证了B1的说法)
C.完成了B的初始化之后,程序运行阶段,四大对象就不再是原始的四大对象,而是经过了增强实现了对应插件功能的代理对象了。
四、设计模式
- 文章标题写了代理模式和责任链模式。代理模式其实在框架中几乎是无处不在,Mybatis中也用的非常多,责任链模式体现在InterceptorChain#pluginAll方法中,对四大对象目标类让插件进行逐个增强,不过感觉不是很明显,具体关于责任链模式可以参考: 03-行为型模式(上)
五、参考