在Mybatis中可以使用拦截器在SQL执行之前进行一系列的自定义操作。比如数据的权限拦截,数据分页,数据执行时一些公共逻辑的植入等等。在讨论这个之前,需要了解Java的动态代理原理和责任链模式。
下面继续从源码的角度来看Interceptor的实现原理,植入拦截器的核心逻辑在Configuration类中,这也是mybatis最为核心的配置类。
下图是流程图:
然后再来分析源码:
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);
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;
}
}
可以看到,在创建ParameterHandler,ResultSetHandler ,StatementHandler ,Executor等四个 实例之后,使用interceptorChain.pluginAll(Object)方法来包装一下再返回。
这里的逻辑是:如果定义了相关的拦截器,就返回一个对应的对象的Java动态代理类。如果没有匹配到的拦截器,就返回本身。
也就是说,可以在这四个mybatis的核心处理器前面加拦截器。
拦截器链源码:
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
//遍历所有拦截器,如果有匹配的拦截器就生成Java动态代理对象,将拦截器中的逻辑封装在其中
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
//添加一个拦截器
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
//获取所有的拦截器
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
拦截器源码:
public interface Interceptor {
//拦截器的核心方法,自定义的逻辑都在这里实现
Object intercept(Invocation invocation) throws Throwable;
//判断是否执行intercept(Invocation invocation)方法和生成Java动态代理类
default Object plugin(Object target) {
//这个逻辑都在Plugin中实现
return Plugin.wrap(target, this);
}
//自定义Interceptor配置
default void setProperties(Properties properties) {
// NOP
}
}
拦截器执行流程控制的核心逻辑在Plugin中,可以看到Plugin实现类InvocationHandler 接口,是一个典型的Java动态代理对象。代理中自定义的核心逻辑在invoke(Object proxy, Method method, Object[] args)方法中。
public class Plugin implements InvocationHandler {
//代理的对象 ,ParameterHandler,ResultSetHandler,StatementHandler,Executor
private final Object target;
//自定义的拦截器实现
private final Interceptor interceptor;
//@Intercepts的解析结果
//例如@Intercepts({@Signature(type = Executor.class, method = "update", args ={MappedStatement.class,Object.class} )})
//解析结果是:key=Executor.class , value={"update"}
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) {
//解析自定义的Interceptor上的注解@Intercepts
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
//代理对象class
Class<?> type = target.getClass();
//比对注解信息和代理对象之间的关系,
//如果@Signature(type,method,args)中的type是代理对象自身或者子类,就返回对应的class
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
//如果匹配成功就创建Java的动态代理
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
/**
*代理核心逻辑,这里根据注解判断是否在执行proxy方法之前是否执行拦截器中的intercept()方法逻辑
*@param proxy 上文中的四大对象之一
*@param method proxy中的某一个方法,在@Signature中method指定方法
*@param args 代理的方法参数
*
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//执行代理对象方法之前,判断在注解中有定义,如果注解没有定义拦截,就不执行拦截拦截,
//只有注解中指明了拦截哪个方法,在哪个方法执行前才会执行拦截其中intercept()方法中逻辑
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) {
//解析注解,如果自定的Interceptor不加@Intercepts注解,则会抛出异常
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
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<>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
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<>();
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()]);
}
}
注意:
1 如果自定的Interceptor不加注解@Intercepts来说明要拦截的方法。则会抛出异常!
2 如果自定义多个拦截器,则会返回一个多层的Java动态代理对象。
3 这里使用的是Java的动态代理,因此要求代理的对象必须是一个接口。以上四个对象都是接口。
到这里拦截器的源码就分析完了,下面来写一个样例验证一下:
首先在pom中添加mybatis的starter
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
配置数据源…
然后自定义一个Interceptor
@Intercepts({
@Signature(type = Executor.class, method = "update", args ={MappedStatement.class,Object.class}),
@Signature(type = Executor.class ,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("----> 执行我的自定义拦截器啦!");
//TODO 可以根据invocation里面包含的接口方法和参数来自定义操作。
System.out.println("----> 自定义逻辑执行完毕!");
return invocation.proceed();
}
}
这个拦截器拦截Executor接口的update和query方法。
在mybatis底层,update方法对应的是数据的insert,delete和update ,query对应的是select
自定义拦截之后再就是将拦截器植入拦截器链。目前非xml配置的方式我发现有两种方式。
① 自定义拦截器上添加@Component注解 或者在配置中使用@Bean注解将其植入spring容器
② 自定义mybatis的配置类
这里使用方法②
@org.springframework.context.annotation.Configuration
@MapperScan(basePackages = {"com.my.order.mapper"})
public class MybatisConfig implements ConfigurationCustomizer {
@Override
public void customize(Configuration configuration) {
//驼峰映射 a_book -> aBook
configuration.setMapUnderscoreToCamelCase(true);
//禁止缓存
configuration.setCacheEnabled(false);
//值为null时不调用setter方法
configuration.setCallSettersOnNulls(false);
//添加自定义拦截器
configuration.addInterceptor(new MyInterceptor());
}
}
这样就将自定的拦截器植入了拦截器链。
再来看一下实际效果。
拦截器在加载相关的接口时是不会调用的,只有在调用了拦截器注解中申明的拦截方法才会触发拦截器中逻辑。
因此,这里我调用一下查询方法,由于上面配置了对“query”的拦截。因此会触发拦截器逻辑。
执行如下逻辑:
<select id="selectById" resultType="stock">
select t.* from t_stock t where t.id=#{id}
</select>
IDEA中打断点结果:
到此,mybatis拦截器的核心原理就分析的很明确了。想要自行定义拦截器,前提是对mybatis这些基础组件有很深入的理解。经典实现可以参考mybatis的分页插件。