解题思路
MyBatis 的插件机制是其核心扩展能力之一,通过动态代理和拦截器模型,允许开发者在 SQL 执行的关键节点插入自定义逻辑(如日志记录、性能监控、SQL 改写等)。以下是其核心原理和实现细节的深度解析:
一、插件运行原理的核心架构
拦截器接口(Interceptor)
o 定义:插件需实现Interceptor接口,包含三个核心方法:
ointercept(Invocation invocation):拦截目标方法,执行自定义逻辑。
oplugin(Object target):生成代理对象,包装目标对象。
osetProperties(Properties properties):设置插件属性(通过 XML 配置传递参数)。
o 作用:拦截器是插件的核心逻辑载体,决定何时触发拦截及如何处理。
动态代理机制
o 代理对象生成:MyBatis 通过 Java 动态代理(Proxy.newProxyInstance)为被拦截的核心组件(如Executor、StatementHandler)生成代理类。
o 方法拦截:当调用代理对象的方法时,会触发InvocationHandler的invoke方法,进而执行插件的intercept方法。
插件链(Interceptor Chain)
o 多插件协作:多个插件可按配置顺序拦截同一方法,形成链式调用。每个插件的intercept方法依次执行,最终调用invocation.proceed()传递到下一个插件或原始方法。
二、插件执行流程详解
1、插件注册
o 配置方式:在mybatis-config.xml中通过<plugins>标签注册插件,或使用@Intercepts注解声明拦截目标。
o 示例配置:
<plugins>
<plugin interceptor="com.example.LoggingInterceptor">
<property name="logLevel" value="DEBUG"/>
</plugin>
</plugins>
2、目标对象包装
o 代理生成时机:MyBatis 初始化时扫描所有插件,根据@Signature注解匹配目标类和方法,生成代理对象。
o 核心组件拦截:插件可作用于以下四个核心接口:
oExecutor:SQL 执行器(如update、query方法)。
oStatementHandler:SQL 语句处理器(如预编译、参数绑定)。
oParameterHandler:参数处理器(参数设置)。
oResultSetHandler:结果集处理器(结果映射)。
3、方法拦截与逻辑增强
o 拦截点选择:通过@Signature注解指定拦截目标:
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
o 逻辑插入:在intercept方法中,可通过invocation.getArgs()获取方法参数,通过invocation.proceed()调用原始方法,实现日志记录、SQL 改写等操作。
三、插件开发示例
- 日志记录插件
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class SqlLogInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
String sql = ms.getBoundSql(parameter).getSql();
System.out.println("Executing SQL: " + sql);
long start = System.currentTimeMillis();
Object result = invocation.proceed();
System.out.println("Execution time: " + (System.currentTimeMillis() - start) + "ms");
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
}
2、分页插件(简化版)
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class PaginationInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler handler = (StatementHandler) invocation.getTarget();
MappedStatement ms = handler.getMappedStatement();
Object parameter = handler.getParameterHandler().getParameterObject();
// 动态改写 SQL 添加分页逻辑(如 LIMIT/OFFSET)
BoundSql boundSql = handler.getBoundSql();
String originalSql = boundSql.getSql();
String pageSql = originalSql + " LIMIT 0, 10"; // 示例分页
// 通过反射修改 BoundSql 中的 SQL
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, pageSql);
return invocation.proceed();
}
}