Mybatis 相关知识传送门
MyBatis 源码分析–SqlSessionFactory
MyBatis 源码分析-- getMapper(获取Mapper)
MyBatis 源码分析-- SQL请求执行流程( Mapper 接口方法的执行的过程)
Mybatis 插件(拦截器)是什么?
MyBatis 提供了一种插件(plugin)的功能,虽然叫做插件,但其实是拦截器功能,使用了 JDK 动态代理和责任链设计模式来实现的,通过 Mybatis 插件可以实现对框架的扩展,让开发者自行扩展,完成逻辑的增加,AOP 功能,基于插件机制可以实现了很多有用的功能,比如说分页,字段加密、SQL 监控等,类似 Spring AOP 一样,横切在数据操作上,并且对于用户是无感知的。
MyBatis 插件(拦截器)支持的扩展接口及方法
- Executor(SQL执行器):update,query,flushStatements,commit,rollback,getTransaction,close,isClosed。
- StatementHandler(SQL 语法构建器对象):prepare,parameterize,batch,update,query。
- ParameterHandler(参数处理器):getParameterObject,setParameters。
- ResultSetHandler(结果处理器):handleResultSets,handleOutputParameters。
MyBatis 拦截器链源码分析
MyBatis 把所有的插件(拦截器)存储在一个 List 中,这些 List 形成了一个拦截器链就是 InterceptorChain,InterceptorChain 的 pluginAll 方法会把传入的对象进行代理,使用的是 JDK 的动态代理。
package org.apache.ibatis.plugin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList();
public InterceptorChain() {
}
public Object pluginAll(Object target) {
Interceptor interceptor;
for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
interceptor = (Interceptor)var2.next();
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
this.interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(this.interceptors);
}
}
InterceptorChain#pluginAll 源码分析
上面我们说 InterceptorChain 的 pluginAll 方法会把传入的对象进行代理,那传入的是什么对象呢?pluginAll 方法什么时候回会被调用呢?我们看下面这段代码,摘自 org.apache.ibatis.session.Configuration。
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler)this.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 = (ResultSetHandler)this.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 = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return this.newExecutor(transaction, this.defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object 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 (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
我们看到 ParameterHandler、ResultSetHandler、StatementHandler、Executor 都调用了 putAll 方法,也就是说这四个 Handler 在创建的时候都会调用 InterceptorChain#pluginAll 方法进行代理,而这四个 Handler 也刚好是 Mybatis 允许我们扩展接口。
InterceptorChain#pluginAll 方法源码分析
InterceptorChain#pluginAll 方法会遍历所有的插件(拦截器),并调用其 plugin 方法,最终会调用 Plugin#wrap 方法。
//org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
Interceptor interceptor;
for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
interceptor = (Interceptor)var2.next();
}
return target;
}
//org.apache.ibatis.plugin.Interceptor#plugin
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
Plugin#wrap 方法源码分析
Plugin#wrap 方法会获取目标对象的所有实现接口,通过 JDK 动态代理生成代理对象,对目标类进行代理,最终的执行器为 Plugin(new Plugin(target, interceptor, signatureMap)),如果没有实现接口,则直接返回目标对象。
//org.apache.ibatis.plugin.Plugin#wrap
public static Object wrap(Object target, Interceptor interceptor) {
//解析该拦截器所拦截的所有接口及对应拦截接口的方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
//获取目标对象的 class
Class<?> type = target.getClass();
//获取目标对象的所有实现接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
//有实现接口 生成代理对象返回 没有实现接口 返回目标对象
return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
}
Plugin#getSignatureMap 方法源码分析
Plugin#getSignatureMap 方法的作用是去获取插件(拦截器)上 @Intercepts 注解的类容,也就是 Mybatis 允许被扩展的接口和方法,组装成一个 Map 返回。
//org.apache.ibatis.plugin.Plugin#getSignatureMap
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
//获取 interceptor上的@Intercepts 注解
Intercepts interceptsAnnotation = (Intercepts)interceptor.getClass().getAnnotation(Intercepts.class);
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
} else {
//获取 @Signature 注解
Signature[] sigs = interceptsAnnotation.value();
//key 为 插件(拦截器) 类型 value 为所有方法
Map<Class<?>, Set<Method>> signatureMap = new HashMap();
Signature[] var4 = sigs;
int var5 = sigs.length;
//遍历 sigs
for(int var6 = 0; var6 < var5; ++var6) {
Signature sig = var4[var6];
//加入 signatureMap
Set methods = (Set)signatureMap.computeIfAbsent(sig.type(), (k) -> {
return new HashSet();
});
try {
//获取被代理的方法
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException var10) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + var10, var10);
}
}
//返回
return signatureMap;
}
}
Plugin#getAllInterfaces 方法源码分析
Plugin#getAllInterfaces 方法遍历了目标对象的所有实现接口,如果该接口需要被代理,就会添加到结果集返回。
//org.apache.ibatis.plugin.Plugin#getAllInterfaces
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
HashSet interfaces;
//遍历目标对象的所有接口
for(interfaces = new HashSet(); type != null; type = type.getSuperclass()) {
Class[] var3 = type.getInterfaces();
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
Class<?> c = var3[var5];
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
}
return (Class[])interfaces.toArray(new Class[0]);
}
Plugin#invoke 方法源码分析
Plugin#wrap 方法会获取目标对象的所有实现接口,通过 JDK 动态代理生成代理对象,对目标类进行代理,最终的执行器为 Plugin,通过JDK 动态代理创建了一个 Plugin 类,Plugin 实现了 InvocationHandler,熟悉动态代理的小伙伴应该都清楚,调用代理类的方法时,会调用invoke 方法,也就是说 Plugin 其实就是一个 InvocationHandler,最终会调用 Plugin#invoke 方法。
//Plugin#invoke 如果该接口需要被代理 就会添加到结果集返回
//org.apache.ibatis.plugin.Plugin#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//signatureMap key 为 插件(拦截器) 类型 value 为所有方法
//从 signatureMap 中获取 method所属类的所有需要被代理拦截的方法
Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
//判断方法是否需要被拦截
//是 :this.interceptor.intercept(new Invocation(this.target, method, args)) 走 interceptor 方法
//否: method.invoke(this.target, args) 直接放行
return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
} catch (Exception var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
分析完 Plugin#invoke 方法,我们就明白了自定义插件(拦截器)为什么要实现 Interceptor 接口,并重写 intercept 方法,因为在执行目标方法之前,会先调用我们自定义的 intercept 方法逻辑,最后再通过 Invocation#proceed 执行代理类的目标方法的原有业务逻辑。
Invocation 源码分析
Invocation 中有一个核心方法 Invocation#proceed,自定义插件(拦截器)执行完自定义的 intercept 方法之后会通过 Invocation#proceed 执行代理类的目标方法的原有业务逻辑。
package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Invocation {
//代理的目标类
private final Object target;
//代理的目标类的目标方法
private final Method method;
//代理的目标类的目标方法的参数
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return this.target;
}
public Method getMethod() {
return this.method;
}
public Object[] getArgs() {
return this.args;
}
//org.apache.ibatis.plugin.Invocation#proceed
public Object proceed() throws InvocationTargetException, IllegalAccessException {
//真正执行代理类的目标方法
return this.method.invoke(this.target, this.args);
}
}
至此,MyBatis 插件(拦截器)原理分析完毕,希望可以帮助到有需要的小伙伴。
面试题解析:
同一个目标类的同一个方法是否可以被多个插件(拦截器)拦截?
答案:可以。
多个插件(拦截器)拦截同一个目标类的同一个方法时候,执行顺序是怎样的?
答案:配置在最前面的插件(拦截器)最后执行,配置在最后面的插件(拦截器)最先被执行。
如有不正确的地方请各位指出纠正。