一、mybatis插件的实现
1. 实现步骤
1.实现Interceptor接口,重写对应方法,主要是Intercept()和setProperties()方法
2.在子类中采用@Intercepts注解,标识要拦截的类和方法
3.在mybatis-config.xml中配置Plugins标签
以pagehelper插件为例来说明:
@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 {
// ... 省略
@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];
}
checkDialectExists();
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
//查询总数
Long count = count(executor, ms, parameter, rowBounds, resultHandler, 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);
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
if(dialect != null){
dialect.afterAll();
}
}
}
// ...省略
}
PageInterceptor 拦截了Executor类的query的两个重载方法。
2. mybatis中可以拦截的类
mybatis的插件支持拦截的类有四个,如下:
parameterHandler
ResultSetHandler
StatementHandler
Executor
从mybatis的源码可知,主要在Configuration类中对这几个对象实例化方法中看出,如下:
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, 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;
}
3. 实现原理
- 在解析xml配置文件的时候,将plugins标签中对应的实现类解析处理放入到interceptorChain集合中。
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历 plugin
for (XNode child : parent.getChildren()) {
// 获取interceptor属性值
String interceptor = child.getStringAttribute("interceptor");
// 获取所有子节点属性值
Properties properties = child.getChildrenAsProperties();
// 实例化Interceptor
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 将对象属性赋值
interceptorInstance.setProperties(properties);
// 加入到Configuration对象的拦截器链中
configuration.addInterceptor(interceptorInstance);
}
}
}
- 在实例化SqlSession时,会实例化Executor,实例化Executor主要就是通过Configuration中的newExecutor方法,如下:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 实例化Executor
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
- newExecutor方法中会通过拦截器链的pluginAll方法,该方法会遍历所有的拦截器,然后匹配是否有拦截当前exector的拦截器类,如果有会使用动态代理返回一个executor的代理对象。
// class: InterceptorChain
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
// plugin方法中主要调用了Plugin对象的wrap方法
public static Object wrap(Object target, Interceptor interceptor) {
// 获取拦截器的签名类和方法方法入到集合中,类对象为key, 方法结合为value
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// target:当前的目标对象,比如现在是CacheExecutor, 获取她的类对象
Class<?> type = target.getClass();
// 通过该方法获取到target实现的接口集合,该方法会通过signatrueMap的key去匹配是否有type的接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 通过动态代理生成一个target的代理对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
- 生成了代理对象之后,所有的方法都会通过Plugin的invoke的处理,如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 如果有方法所属的类有拦截器,统一通过拦截器的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);
}
}