详解Mybatis插件模块及分页插件PageHelper
Mybatis 插件功能,是通过拦截器(Interceptor)实现的,应用了简单工厂模式、责任链模式和动态代理模式。分页是插件模块常见的应用场景,PageHelper是最常用的分页插件之一。
Mybatis 简单介绍
本部分简单介绍一下Mybatis的初始化流程,适合只有Mybatis使用经验而没有源码阅读经验的同学。 在Mybatis的初始化过程中,主要通过读取配置文件,创建相应的配置对象,最终完成框架各个模块的初始化。读取的配置信息,会被封装到Configuration对象的对应属性中。
XMLConfigBuilder 主要负责解析配置文件
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfsImpl(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginsElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlersElement(root.evalNode("typeHandlers"));
mappersElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
当所有信息解析完成封装到Configuration后,由Configuration创建相应的对象,其中插件功能是通过创建Executor、ParameterHandler、ResultSetHandler、StatementHandler的代理对象实现。
Interceptor
Mybatis 允许用户自定义拦截器对SQL语句执行过程中的某一点进行拦截,允许拦截器拦截的方法包括:Executor中的方法、ParameterHandler中的方法、ResultSetHandler中的方法、StatementHandler中的方法。
Mybatis中使用拦截器都需要实现 Interceptor
接口,其定义如下:
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
}
}
用户自定义拦截器,还需要使用 @Intercepts
和 @Signature
两个注解标识
- @Intercepts 注解:指定一个
@Signature
注解列表 - @Signature:每个
@Signature
注解中都标识了该插件需要拦截的方法信息,其中@Signature
注解的 type 属性指定需要拦截的类型,method 属性指定需要拦截的方法,args 属性指定被拦截方法的参数列表。通过这3个注解,@Signature
就可以表示一个方法签名,唯一确定一个方法。
例如 PageHelper 插件中定义的 PageInterceptor
拦截器,该拦截器可以拦截 Executor 接口中的两个 query 方法。
@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 PageInterceptor implements Interceptor {
private static final Log log = LogFactory.getLog(PageInterceptor.class);
}
在 Mybatis 初始化时,通过 MLConfigBuilder#pluginsElement()
方法解析配置文件中的 <plugins>
节点,得到相应的 Interceptor
对象,以及配置的相关属性,然后调用 Interceptor.setProperties(properties) 方法完成对 Interceptor 对象的初始化配置,最后将 Interceptor 对象添加到 Configuration#interceptorChain
(InterceptorChain 类型)字段,InterceptorChain 底层使用ArrayList<Interceptor>实现。
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
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);
}
}
Configuration#new*()方法
以 Configuration.newExecutor() 为例进行分析:
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);
}
// interceptorChain.pluginAll 方法创建的 Executor 的代理对象
return (Executor) interceptorChain.pluginAll(executor);
}
InterceptorChain 中使用 interceptors 字段(ArrayList<Interceptor> 类型)记录了配置的拦截器。在 InterceptorChain#pluginAll
方法中,会遍历 interceptors 集合中的每个元素,调用其中的 plugin()
方法创建代理对象,代码如下:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
用户自定义拦截器实现的 plugin()
方法,可以直接使用 Plugin 工具类实现,它实现了 InvocationHandler
接口,并提供了一个 wrap() 静态方法用于创建代理对象,
该方法的返回值即为创建的代理对象,
方法接受2个参数:
-
Object target:目标对象,例如当前是从newExecutor() 方法进来的,那么这个 target 类型就可能是如下类型的一种:BatchExecutor、ReuseExecutor、SimpleExecutor、CachingExecutor
-
Interceptor interceptor:拦截器,例如分页插件的拦截器 PageInterceptor
public static Object wrap(Object target, Interceptor interceptor) {
// 获取用户自定义 Interceptor 中 @Signature 注解信息
// getSignatureMap() 方法负责处理 @Signature 注解 ,具体实现见下方
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 获取目标类型
Class<?> type = target.getClass();
// 获取目标类型实现的接口,例如当前是从newExecutor() 方法进来的,获取的就是 Executor
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 使用 JDK动态代理的方式创建代理对象(不了解JDK动态代理的,强烈安利阅读上述提到的文章)
// new Plugin(target, interceptor, signatureMap) 这里的 Plugin 是 InvocationHandler 的实现
return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
}
return target;
}
getSignatureMap() 方法实现:
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
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 = MapUtil.computeIfAbsent(signatureMap, 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;
}
Plugin
JDK 动态代理的实现,通过 InvocationHandler
实现增强逻辑,Proxy创建代理对象。Plugin 实现了 InvocationHandler。
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
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;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 获取当前方法所在类或接口中,可被当前 Interceptor 拦截的方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 如果当前调用的方法需要被拦截,则调用 interceptor.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);
}
}
Plugin 中定义了3个字段:
- target:目标对象
- interceptor:Interceptor 对象
- signatureMap:记录了 @Signature 注解中的信息
Plugin.invoke()
方法在代理对象被调用的时候执行,当需要执行增强逻辑的时候,会调用 nterceptor#intercept
方法。以下是 PageInterceptor
中 intercept 的实现,源码中写了详细的注释,就不一一展开了。
public Object intercept(Invocation invocation) throws Throwable {
try {
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) {
//4 个参数时
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
checkDialectExists();
//对 boundSql 的拦截处理
if (dialect instanceof BoundSqlInterceptor.Chain) {
boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);
}
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
//开启debug时,输出触发当前分页执行时的PageHelper调用堆栈
// 如果和当前调用堆栈不一致,说明在启用分页后没有消费,当前线程再次执行时消费,调用堆栈显示的方法使用不安全
debugStackTraceLog();
Future<Long> countFuture = null;
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
if (dialect.isAsyncCount()) {
countFuture = asyncCount(ms, boundSql, parameter, rowBounds);
} else {
//查询总数
Long count = count(executor, ms, parameter, rowBounds, null, boundSql);
//处理查询总数,返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
}
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
if (countFuture != null) {
Long count = countFuture.get();
dialect.afterCount(count, parameter, rowBounds);
}
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
if (dialect != null) {
dialect.afterAll();
}
}
}
转载自:https://juejin.cn/post/7387226606414069769