本节我们来介绍 Mybatis 的拦截器 Interceptor
,它依靠 @Intercepts
和 @Signature
注解驱动,配置拦截器的切入方法,这种声明方式非常直观,能够准确的知道每个拦截器的作用范围。而且它是非侵入性的,采用了 动态代理模式,在不修改原有逻辑的前提下便能实现功能的扩展,遵循 开闭原则。Spring 框架中的 AOP 也采用的是同样的思想,但是它引入了很多概念(切面、连接点、切入点和通知等等),代码量超过 5000 行,而 Mybatis Interceptor
的实现仅有 370 行,非常精简,接下来我们先介绍下它的原理。
首先是解析 mybatis-config.xml
配置文件中定义的拦截器方法 Configuration#pluginsElement
,解析创建完成后会被保存在 InterceptorChain interceptorChain
对象中:
public class Configuration {
// ...
protected final InterceptorChain interceptorChain = new InterceptorChain();
private void pluginsElement(XNode context) throws Exception {
if (context != null) {
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);
}
}
}
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
}
那这些拦截器在哪里生效呢?其实在前面我们讲 SQL 执行流程时,已经见过它的身影,仍然是在 Configuration
中,创建四大处理器时都能看到 pluginAll
方法的身影(从这里也能知道拦截器的作用范围便是这四大拦截器):
public class Configuration {
// ...
protected final InterceptorChain interceptorChain = new InterceptorChain();
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject,
BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,
parameterObject, boundSql);
// 拦截器相关逻辑
return (ParameterHandler) interceptorChain.pluginAll(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);
// 拦截器相关逻辑
return (ResultSetHandler) interceptorChain.pluginAll(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);
// 拦截器相关逻辑
return (StatementHandler) interceptorChain.pluginAll(statementHandler);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
// 创建具体的 Executor 实现类
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);
}
}
在每个处理器创建完成后都会执行 pluginAll
方法,接着我们看一下 InterceptorChain#pluginAll
方法:
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 注意 target 引用不断变化,会不断引用已经添加拦截器的对象
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
InterceptorChain
实现非常简单,内部定义了集合来保存所有配置的拦截器,执行 pluginAll
方法时会遍历该集合,逐个调用 Interceptor#plugin
方法来不断添加拦截器。注意这里使用到了 责任链模式,由 InterceptorChain
的命名中包含 Chain
也能联想到该模式,之后我们在使用责任链时也可以考虑在命名中增加 Chain
以增加可读性。InterceptorChain
将多个拦截器串联在一起,每个拦截器负责其特定的逻辑处理,并在执行完自己的逻辑后,调用下一个拦截器或目标方法,这样设计允许不同的拦截器之间的逻辑 解耦,同时提供了 可扩展性。
接着我们再看 Interceptor#plugin
方法:
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
}
}
它是定义在接口中的 default
方法,在实际业务开发中还是较少看见这个关键字的。继续探究 Plugin#wrap
方法:
public class Plugin implements InvocationHandler {
// 被拦截器代理对象或被代理的拦截器
private final Object target;
// 代理拦截器本身
private final Interceptor interceptor;
// 要被拦截的方法 Map
private final 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) {
// 根据 Intercepts 注解和 Signature 注解获取要被拦截的方法
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);
}
}
// ...
}
Plugin
实现了 InvocationHandler
接口,使用了 动态代理模式,是拦截器实现的核心类。在 wrap
方法中,会根据 @Intercepts
和 @Signature
来定位到要被拦截的方法,因为这部分逻辑很简单,便不再赘述了,后续便会创建 Plugin
代理对象。在 invoke
方法中,它会判断要执行的方法是否为被拦截的方法,并执行对应的逻辑,在执行拦截器逻辑时,它并没有直接将 method
、target
和 args
参数直接暴露出去供拦截器做逻辑,而是将它们 封装 起来为 Invocation
对象:
public class Invocation {
private static final List<Class<?>> targetClasses = Arrays.asList(Executor.class, ParameterHandler.class,
ResultSetHandler.class, StatementHandler.class);
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
if (!targetClasses.contains(method.getDeclaringClass())) {
throw new IllegalArgumentException("Method '" + method + "' is not supported as a plugin target.");
}
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
Invocation
提供了 Get 方法来获取这些元素,并没有将它们全部都隐藏起来,而且也提供了方便的 process
方法调用,在构造方法中还添加了对应的校验,降低了 复杂度,这样在实现拦截器的大部分情况下,只需执行 process
方法即可,简单易用。
到现在为止拦截器的实现原理已经介绍完了,我们来看一下拦截器使用的例子,定义两个拦截器,以 org.apache.ibatis.plugin.PluginTest
为例:
class PluginTest {
// 更换库名
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
public static class SwitchCatalogInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
Connection con = (Connection) args[0];
con.setSchema(SchemaHolder.get());
return invocation.proceed();
}
}
// 打印执行前、后的日志
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
public class LogInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("Before Process...");
Object res = invocation.proceed();
System.out.println("After Process...");
return res;
}
}
}
在 mybatis-config.xml
中定义的拦截器顺序如下,在向 interceptorChain
中添加时 SwitchCatalogInterceptor
在前,LogInterceptor
在后:
<configuration>
<!-- ...-->
<plugins>
<plugin interceptor="org.apache.ibatis.plugin.PluginTest$SwitchCatalogInterceptor" />
<plugin interceptor="org.apache.ibatis.plugin.PluginTest$LogInterceptor" />
</plugins>
</configuration>
那么在应用拦截器时 越定义在后面的拦截器越会先生效,如下所示,LogInterceptor
在 SwitchCatalogInterceptor
外层:
如果要考虑拦截器的执行顺序,那么便需要在添加拦截器时多注意了。