Mybatis的插件机制简介
插件机制概括
Mybatis插件实现核心:
JDK动态代理(基于接口),以此织入插件代码逻辑;因此,插件是业务无关的,无感知植入,无形中进行功能增强。
Mybatis允许拦截的方法
Executor
- update
- query
- flushStatements
- commit
- rollback
- getTransaction
- close
- isClose
ParameterHandler
- getParameterObject
- setParameter
ResultHandler
- handleResultSets
- handleOutputParameters
StatementHandler
- preper
- parameterize
- batch
- update
- query
插件的实现步骤
编写
- 创建插件类,实现Interceptor接口
- 通过注解标注该插件类的拦截点
插件生效
配置类中实例化插件
插件示例
springboot框架下,实现一个简单的分页插件
编写插件类
@Intercepts({
@Signature(
type = Executor.class, // 目标类
method = "query", // 目标方法
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
})
public class MySqlPagingPlugin implements Interceptor {
private static final Integer MAPPED_STATEMENT_INDEX = 0;
private static final Integer PARAMETER_INDEX = 1;
private static final Integer ROW_BOUNDS_INDEX = 2;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
RowBounds rb = (RowBounds) args[ROW_BOUNDS_INDEX];
// 无需分页
if (rb == RowBounds.DEFAULT) {
return invocation.proceed();
}
// 将原 RowBounds 参数设为 RowBounds.DEFAULT,关闭 MyBatis 内置的分页机制
args[ROW_BOUNDS_INDEX] = RowBounds.DEFAULT;
MappedStatement ms = (MappedStatement) args[MAPPED_STATEMENT_INDEX];
BoundSql boundSql = ms.getBoundSql(args[PARAMETER_INDEX]);
// 获取 SQL 语句,拼接 limit 语句
String sql = boundSql.getSql();
String limit = String.format("LIMIT %d,%d", rb.getOffset(), rb.getLimit());
sql = sql + " " + limit;
// 创建一个 StaticSqlSource,并将拼接好的 sql 传入
SqlSource sqlSource = new StaticSqlSource(ms.getConfiguration(), sql, boundSql.getParameterMappings());
// 通过反射获取并设置 MappedStatement 的 sqlSource 字段
Field field = MappedStatement.class.getDeclaredField("sqlSource");
field.setAccessible(true);
field.set(ms, sqlSource);
// 执行被拦截方法
return invocation.proceed();
}
//默认方法
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
intercept方法中编写插件增强的功能代码,没有必要可以不重写plugin、setProperties方法,使用Interceptor接口默认实现
配置插件启动生效
@Configuration
public class MybatisPlusConfig {
@Bean
public MySqlPagingPlugin paginationInterceptor() {
MySqlPagingPlugin paginationInterceptor = new MySqlPagingPlugin();
return paginationInterceptor;
}
}
启动时实例化一个插件实例即可
插件功能源码分析
以分页插件为分析入口,拦截的Executor类的query方法
执行器Executor的创建
//类 DefaultSqlSessionFactory
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);
//创建执行器
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();
}
}
//类 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 = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
//类 InterceptorChain
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
//接口Interceptor plugin方法默认实现
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
//类 Plugin生成代理类
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
//类 Plugin执行织入了增强逻辑的切点方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
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);
}
}
拦截器链示意图
执行顺序是由外向内,限制性外层插件逻辑,最后执行Executor
关键方法invoke,首先检测被拦截方法是否配置在插件的@Signature注解中;若是,执行插件逻辑,在执行被拦截方法;若不是,则直接执行被拦拦截方法。