插件简介
一般情况下,开源框架都会提供插件或者扩展点,供开发者自行拓展。这样的好处显而易见,一是增加了框架的灵活性,二是开发者结合实际需求,对框架进行扩展,使它能够更好的工作。
以 MyBatis 为例子,我们可以基于 MyBatis 插件机制实现分页、分表、监控功能。由于插件和业务无关,业务也无法感知插件的存在,因此可以无感植入插件,在无形中实现增强。
MyBatis 插件机制允许用户通过实现插件接口来扩展 MyBatis 的功能,拦截 SQL 语句的执行过程,进行自定义的处理。
基本概念
MyBatis 插件机制基于 Java 的拦截器模式,通过插件类实现对 MyBatis 核心行为的拦截和修改。插件可以在执行特定的操作时(如查询、更新、删除等)插入自定义代码。
作用范围
插件的作用对象是 MyBatis 内部的核心组件,如 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler:
- Executor:执行 SQL 的操作
- StatementHandler:处理 SQL 语句的生成、参数设置等
- ResultSetHandler:处理结果集的映射
- ParameterHandler:处理 SQL 语句的参数设置
常见场景
- SQL 日志记录
- SQL 性能监控
- 事务控制
- 缓存
- SQL 改写
优点
- 灵活性:允许开发者在不修改 MyBatis 核心代码的情况下灵活地增加或修改功能
- 扩展性:可以轻松地扩展 MyBatis,满足各种业务需求
- 可定制性:可以根据业务需求定制插件
缺点
- 调试复杂:由于插件是通过代理模式实现的,调试时可能会增加复杂性
- 性能开销:每次 SQL 执行时都需要经过插件的拦截和处理
插件介绍
MyBatis 作为一个广泛的优秀的 ORM 开源框架,在四大组件:Executor、StatementHandler、ParameterHandler、ResultSetHandler,提供了简易易用的插件扩展机制。
MyBatis 允许拦截的方法如下:
- 执行器 Executor:update, query, commit, rollback 等等
- SQL 语法构建器 StatementHandler:prepare、parameterize、batch、updates query 等方法
- 参数处理器 ParameterHandler:getParameterObject, setParameters 方法
- 结果集处理器 ResultSetHandler:handlerResultSets, handleOutputParameters 等方法
插件原理
在四大核心对象创建的时候,每个创建出来都不是直接返回的,而是 interceptorChain.pluginAll(parameterHandler)。
public ParameterHandler createParameterHandler(
MappedStatement mappedStatement,
Object parameter,
BoundSql boundSql,
InterceptorChain interceptorChain) {
ParameterHandler parameterHandler =
mappedStatement.getLang().createParameterHandler(mappedStatement, parameter, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.applyInterceptors(parameterHandler);
return parameterHandler;
}
public Object applyInterceptors(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
interceptorChain 保存了所有的拦截器,调用拦截器链中的拦截器依次对目标进行拦截或增强。
自定义插件
MyBatis 插件接口 Interceptor 包含三个方法:
interceptor方法:插件的核心方法plugin方法:生成 target 的代理对象setProperties方法:传递插件所需参数
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
})
public class ExamplePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
}
配置插件:
<plugins>
<plugin interceptor="icu.wzk.interceptor.ExamplePlugin"></plugin>
</plugins>
源码分析
Plugin 实现了 InvocationHandler 接口,invoke 方法会对所有拦截的方法进行检测:
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);
}
}
Invocation 类用于存储目标类、方法以及方法参数列表:
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}