首页 > 基础资料 博客日记
Java-14 深入浅出 MyBatis - 插件机制 插件原理 自定义插件 源码分析
2025-01-15 10:30:08基础资料围观58次
点一下关注吧!!!非常感谢!!持续更新!!!
大数据篇正在更新!https://blog.csdn.net/w776341482/category_12713819.html
目前已经更新到了:
- MyBatis(正在更新)
插件简介
一般情况下,开源框架都会提供插件或者扩展点,供开发者自行拓展。这样的好处显而易见,一是增加了框架的灵活性,二是开发者结合实际需求,对框架进行扩展,使它能够更好的工作。
以 MyBatis 为例子,我们可以基于 MyBatis 插件机制实现分页、分表、监控功能。由于插件和业务无关,业务也无法感知插件的存在,因此可以无感植入插件,在无形中实现增强。
MyBatis 插件机制允许用户通过实现插件接口来扩展 MyBatis 的功能,拦截 SQL 语句的执行过程,进行自定义的处理。这是 MyBatis 提供的一种灵活的机制,可以在不修改核心代码的情况下对其行为进行定制。插件可以用于日志记录、性能监控、SQL 统计等多个场景。
基本概念
MyBatis 插件机制基于 Java 的 拦截器模式,通过插件类实现对 MyBatis 核心行为的拦截和修改。插件可以在执行特定的操作时(如查询、更新、删除等)插入自定义代码。常见的插件场景包括:
- 拦截执行 SQL 的过程:可以修改、增强或记录 SQL 执行的细节。
- 增加额外的处理逻辑:比如日志、缓存、性能监控等。
作用范围
插件的作用对象是 MyBatis 内部的核心组件,如 Executor、StatementHandler、ResultSetHandler 和 ParameterHandler。你可以选择拦截这些组件的具体操作,例如:
- Executor:执行 SQL 的操作。
- StatementHandler:处理 SQL 语句的生成、参数设置等。
- ResultSetHandler:处理结果集的映射。
- ParameterHandler:处理 SQL 语句的参数设置。
插件通过 intercept 方法拦截目标对象,决定是否执行后续操作或修改返回值。
常见场景
- SQL 日志记录: 记录执行的 SQL 语句和执行时间,有助于进行性能监控和问题排查。
- SQL 性能监控: 插件可以记录 SQL 执行的时间,帮助开发者识别性能瓶颈,优化 SQL 执行。
- 事务控制: 插件可以用来在执行 SQL 前后添加自定义的事务控制逻辑,如重试机制等。
- 缓存: 插件可以用于实现二级缓存或其他缓存机制,避免重复查询相同数据。
- SQL 改写: 插件可以修改 SQL 语句,例如添加某些条件、修改排序等,扩展 MyBatis 的查询功能。
优点
- 灵活性:插件机制允许开发者在不修改 MyBatis 核心代码的情况下,灵活地增加或修改功能。
- 扩展性:插件可以轻松地扩展 MyBatis,满足各种业务需求。
- 可定制性:可以根据业务需求定制插件,实现特定的功能,如日志记录、性能监控等。
缺点
- 调试复杂:由于插件是通过代理模式实现的,调试时可能会增加复杂性。
- 性能开销:每次 SQL 执行时都需要经过插件的拦截和处理,可能会引入一定的性能开销。
插件介绍
MyBatis 作为一个广泛的优秀的 ORM 开源框架,这个框架具有强大的灵活性,在四大组件:Executor、StatementHandler、ParameterHandler、ResultSetHandler,提供了简易易用的插件扩展机制。
MyBatis 对持久层的操作就是依赖这四个核心对象,MyBatis 支持用插件对四大核心对象进行拦截,对 MyBatis 来说就是拦截器,增强核心对象的功能,增加功能本质上借助底层的动态代理来实现的,换句话说,MyBatis 中的四大对象都是代理对象。
MyBatis 允许拦截的方法如下:
- 执行器 Executor:update query commit rollback 等等
- SQL 语法构建器 StatementHandler:prepare、parameterize、batch、updates query 等方法
- 参数处理器 ParameterHandler:getParameterObject setParameters 方法
- 结果集处理器 ResultSetHandler:handlerResultSets handleOutputParameters 等方法
插件原理
在四大核心对象创建的时候:
- 每个创建出来都不是直接返回的,而是 interceptorChain.pluginAll(parameterHandler)
- 获取到所有 interceptor,调用 interceptor.plugin(target)返回 target 包装后的对象
- 插件机制,可以使用插件为目标对象创建一个代理对象,AOP(面向切面)我们的插件可以为四大对象创建出代理对象,代理对象可以拦截到四大对象的每一个执行。
插件具体是如何拦截并附加额外的功能呢?比如 ParameterHandler:
public ParameterHandler createParameterHandler(
MappedStatement mappedStatement,
Object parameter,
BoundSql boundSql,
InterceptorChain interceptorChain) {
// 创建 ParameterHandler
ParameterHandler parameterHandler =
mappedStatement.getLang().createParameterHandler(mappedStatement, parameter, boundSql);
// 通过拦截链对 ParameterHandler 进行增强
parameterHandler = (ParameterHandler) interceptorChain.applyInterceptors(parameterHandler);
return parameterHandler;
}
public Object applyInterceptors(Object target) {
// 对目标对象依次应用所有拦截器
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
interceptorChain 保存了所有的拦截器(interceptors),是 MyBatis 初始化的时候创建的,调用拦截器链中的拦截器依次对目标进行拦截或增强。interceptor.plugin(target)中的 target 就可以理解为 MyBatis 中的四大对象,返回的 target 就是被多重代理后的对象。
如果我们想要拦截 Executor 的 query 方法,那么可以定义:
@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) {
// 设置插件的配置参数
}
}
除此之外,我们还需要将插件配置到 sqlMapConfig.xml 上:
<plugins>
<plugin interceptor="icu.wzk.interceptor.ExamplePlugin"></plugin>
</plugins>
这样 MyBatis 在启动时可以加载插件,并保存插件实例相关对象(InterceptorChain,拦截器)。待准备工作结束后,MyBatis 对于就绪状态,我们在执行 SQL 时,需要先通过 DefaultSqlSessionFactory 创建 SQL Session。Executor 实例会在创建 SqlSession 的过程中被创建,Executor 实例创建完毕后,MyBatis 会通过 JDK 动态代理为实例生成代理类,这样,插件逻辑可以在 Executor 相关方法开始调用前执行。
自定义插件
MyBatis 插件接口 interceptor:
- interceptor 方法,插件的核心方法
- plugin 方法,生成 target 的代理对象
- setProperties 方法,传递插件所需参数
源码分析
插件执行逻辑,Plugin 实现了 InvocationHandler 接口,因此它的 Invoke 方法会拦截所有的方法调用,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);
}
}
对应的截图如下所示:
可以看到,invoke 方法的代码比较少,逻辑不难理解,首先,invoke方法会检测被拦截方法,插件逻辑封装在 interceptor 中,该方法的参数类型为 Invocation 主要用于存储目标类,方法以及方法参数列表,下面简单看一下该类的定义:
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
对应的截图如下所示:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签: