XMLConfigBuilder#pluginElement()方法
XMLConfigBuilder#pluginElement()
if (parent != null) {
// 遍历子节点
for (XNode child : parent.getChildren()) {
// 获取interceptor标签下拦截器的全限定类名
String interceptor = child.getStringAttribute("interceptor");
// 获取子节点对应的属性
Properties properties = child.getChildrenAsProperties();
// 通过上面获取得拦截器的全限定类名构建一个拦截器实例
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 将子节点对应的属性设置到拦截器
interceptorInstance.setProperties(properties);
// 将该拦截器实例设置到Configuration对象
configuration.addInterceptor(interceptorInstance);
}
}
}
Interceptor接口
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
可以看出这是一个接口,其中定义了 intercept()、plugin() 以及 setProperties() 三个方法:
- intercept() 方法:覆盖所拦截对象原有的方法,也是插件的核心方法。其入参是 invocation,可以利用反射调原对象的方法;
- plugin() 方法:入参 target 是被拦截的对象,该方法的作用是生成一个被拦截对象的代理实例;
- setProperties() 方法:方便把 MyBatis 配置文件中 plugin 标签下的内容解析后设置到 Configuration 对象。
- 对于一个插件来说,必须要先实现 Intercept 接口中的三个方法才能在 MyBatis 框架中进行配置并使用。
从PageHelper源码来看插件的实现流程 本节中会从 PageHelper 的源代码来分析 MyBatis 插件的实现流程,找到关键类 PageInterceptor 的源码如下:
@SuppressWarnings({"rawtypes", "unchecked"})
// 拦截器的注解
@Intercepts(
{
// 注册拦截器签名:指定需要被拦截的类型(type)、方法(method)和参数(args)需要被拦截
// 只包含4个参数
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
// 只包含6个参数
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
public class PageInterceptor implements Interceptor {
// 缓存count查询的ms
protected Cache<String, MappedStatement> msCountMap = null;
private Dialect dialect;
private String default_dialect_class = "com.github.pagehelper.PageHelper";
private Field additionalParametersField;
private String countSuffix = "_COUNT";
@Override
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];
}
List resultList;
// 调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
// 反射获取动态参数
String msId = ms.getId();
Configuration configuration = ms.getConfiguration();
Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
// 判断是否需要进行count查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
String countMsId = msId + countSuffix;
Long count;
// 先判断是否存在手写的count查询
MappedStatement countMs = getExistedMappedStatement(configuration, countMsId);
if(countMs != null){
count = executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
} else {
countMs = msCountMap.get(countMsId);
// 自动创建
if (countMs == null) {
// 根据当前的ms创建一个返回值为Long类型的ms
countMs = MSUtils.newCountMappedStatement(ms, countMsId);
msCountMap.put(countMsId, countMs);
}
count = executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler);
}
// 处理查询总数
// 返回true时继续分页查询,false时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
// 当查询总数为0时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
// 判断是否需要进行分页查询
if (dialect.beforePage(ms, parameter, rowBounds)) {
// 生成分页的缓存key
CacheKey pageKey = cacheKey;
// 处理参数对象
parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
// 调用方言获取分页sql
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
BoundSql pageBoundSql = new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter);
// 设置动态参数
for (String key : additionalParameters.keySet()) {
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
// 执行分页查询
resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
// 不执行分页的情况下,也不执行内存分页
resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
} else {
// rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
dialect.afterAll();
}
}
// 省略……
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 缓存count ms
msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
String dialectClass = properties.getProperty("dialect");
if (StringUtil.isEmpty(dialectClass)) {
dialectClass = default_dialect_class;
}
try {
Class<?> aClass = Class.forName(dialectClass);
dialect = (Dialect) aClass.newInstance();
} catch (Exception e) {
throw new PageException(e);
}
dialect.setProperties(properties);
String countSuffix = properties.getProperty("countSuffix");
if (StringUtil.isNotEmpty(countSuffix)) {
this.countSuffix = countSuffix;
}
try {
// 反射获取BoundSql中的additionalParameters属性
additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
additionalParametersField.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new PageException(e);
}
}
}
简单看完 PageHelper 插件的实现代码之后,总结一下 MyBatis 框架中自定义插件的步骤:
- 确定要被拦截的签名:根据 @Intercepts 以及 @Signature 注解指定需要拦截的参数;
- 实现 Intercept 接口的 intercept()、plugin() 以及 setProperties() 方法。