Mybatis拦截器源码深度解析
Posted ryanlikecode
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis拦截器源码深度解析相关的知识,希望对你有一定的参考价值。
目录:
Mybatis拦截器 可以帮助我们在执行sql语句过程中增加插件以实现一些通用的逻辑,比如对查询sql分页、数据权限处理等。
允许使用插件拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
各方法的具体作用可以通过 Mybatis之sqlSession调用链分析 进行了解。
方法调用时加载拦截器链的总体时序图如下:
一. 建立拦截器链
1. 创建对象
建立拦截器对象,对Executor接口的实现类上的update方法调用进行拦截:
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
Mybaits中通过注解Intercepts来设定当前拦截器是否对被拦截的请求进行处理,例子中的注解指定对Executor的所有update方法进行拦截处理。
- Intercepts
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
Signature[] value();
}
- Signature
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
Class<?> type();
String method();
Class<?>[] args();
}
对注解的解析在Plugin类中。
2. 建立配置文件
可以通过XMl和注解两种方式进行配置,通过解析配置建立拦截器链。
- xml配置:
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
- 注解配置:
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
3. 加载拦截器链
以XML配置好拦截器好,从XML解析源码中,可以看到通过xml标签属性 interceptor 把拦截器加入 Configuration的 InterceptorChain中,
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);
}
}
}
二. 方法调用解析
之前我们配置了一个对Executor的update方法进行拦截的插件,那么看下具体的执行过程。在调用Configuration生成执行对象时,通过拦截器链对对象进行包装
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进行包装,因为使用了JDK的动态代理,所以返回对象必须为接口
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
1. 对请求对象进行拦截器包装
通过InterceptorChain的pluginAll方法,对interceptors集合循环,依次对target(也就是上面传入的executor对象)进行代理:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// target通过拦截器链,循环迭代代理
target = interceptor.plugin(target);
}
return target;
}
自定义的拦截器需要实现Interceptor实现的plugin方法,该方法用来给target增加代理,推荐直接调用Plugin.wrap(target, this)方法(这里将每一个代理对象的创建放在Plugin类中的静态方法,但是每新增一个插件都需要写这个方法)
@Override
public Object plugin(Object target) {
// 注意第二个参数 为this,通过回调自己,将参数传递给Plugin对象,plugin代理对象执行时,如果符合条件,将回调target的intercept,参见本文2小节
return Plugin.wrap(target, this);
}
Plugin实现了InvocationHandler接口,其类图如下:
Plugin的wrap方法用来创建target的代理对象:
// 参数interceptor为 ExamplePlugin类对象
public static Object wrap(Object target, Interceptor interceptor) {
// 1. 获取注解中的配置,class对应Signature中的type属性,Set<Method>对应Signature中的method属性
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 2. 获取被代理对象的class类对象,这里为Executor的接口实现类
Class<?> type = target.getClass();
// 3. 获取符合对象target接口的拦截器(这里为Executor.class)
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 如果注解包含目标对象接口,对Plugin对象进行代理,返回的Plugin代理对象,调用目标方法时会进行Pluing的invoke()方法
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
// 代理对象传入参数包括 目标类、该拦截器对象、注解配置解析Map
new Plugin(target, interceptor, signatureMap));
}
// 如果该插件注解中TYPE值不包含目标对象接口,则不处理,直接返回目标对象
return target;
}
其中getAllInterFaces方法用来判断判断注解中是否包含目标对象接口:
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
// 如果传入Executor的实现类,那么这里为Executor接口
for (Class<?> c : type.getInterfaces()) {
// 1. 判断注解中是否包含目标对象接口
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
// 2. 如果继承父接口,继续循环判断
type = type.getSuperclass();
}
// 返回该插件注解与被代理对象匹配的所有的接口类对象数组
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
2. 执行调用
当调用被代理对象Execute的所有方法,都会进入Plugin的invoke方法:
// 代理类只用来做流程判断,不增加具体的业务逻辑,业务逻辑统一在实现Invocation接口的插件类中增加
// 对该方法进行递归调用(包括所有已加载进拦截器链中的拦截器,首先判断当前拦截器中的注解条件是否满足,满足的话执行当前拦截器,否则调用target的方法,进入下一个拦截器)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 判断Signature参数method是否包含当前调用方法,如果包含,进入拦截器intercept方法;否则,跳过该拦截器继续运行
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);
}
}
自定义拦截器中intercept实现如下:
public Object intercept(Invocation invocation) throws Throwable {
// TODO 增加自己的业务需求
return invocation.proceed();
}
Invocation相当于责任链中的请求类,其封装反射参数,包含target(target通过InterceptorChain的pluginAll循环代理,因此可以是拦截器的代理类,当经过最后一个拦截器时,为实际调用对象)、method、args,该类主要用来减少intercept方法调用时传入的参数数量
其中proceed方法如下:
public Object proceed() throws InvocationTargetException, IllegalAccessException {
// 通过反射执行target的方法
// 如果target依旧为Plugin代理类,则继续进行代理类Plugin中的invoke方法中
return method.invoke(target, args);
}
三. 小结
Mybatis拦截器主要实现以下几个点:
- 通过注解判断被拦截的请求是否符合当前的拦截器。
- 支持横向扩展,可以自定义拦截器并加入到拦截器链中。
支持请求在拦截器链中依次传递(Invocation类)。
其结合责任链模式使请求和处理解耦,但是每一次请求都要通过责任链上的所有拦截器,也就是一次调用需要所有拦截器进行判断,因此也有一些局限性。
相关文章:
一文读懂JDBC
mybaits动态代理之最小demo实现
Mybatis之sqlSession调用链分析
了解更多请关注微信公众号:
以上是关于Mybatis拦截器源码深度解析的主要内容,如果未能解决你的问题,请参考以下文章