Mybatis 源码学习-Plugin 使用
大约 2 分钟
MyBatis 的 Plugin 机制是其扩展性的核心设计之一,通过拦截器(Interceptor)实现对 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler 四大核心组件的动态代理。以下从 使用示例 和 源码分析 两个部分详细说明。
一、Plugin 使用示例
1. 实现自定义拦截器
以下是一个简单的性能监控插件示例,用于统计 SQL 执行时间:
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
),
@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
)
})
public class PerformanceInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed(); // 执行原始方法
long end = System.currentTimeMillis();
System.out.println("SQL执行耗时: " + (end - start) + "ms");
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this); // 生成代理对象
}
@Override
public void setProperties(Properties properties) {
// 可读取配置文件中的参数
}
}
2. 注册拦截器
在 MyBatis 配置文件中添加插件:
<plugins>
<plugin interceptor="com.example.PerformanceInterceptor">
<!-- 可传递参数 -->
<property name="threshold" value="100"/>
</plugin>
</plugins>
二、Plugin 源码解析
1. 拦截器加载过程
MyBatis 在启动时会解析配置文件中的 <plugins>
标签,通过 InterceptorChain
类管理所有拦截器。
关键源码(XMLConfigBuilder.java
):
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
2. 代理对象生成
当创建 Executor 等四大组件时,会调用 InterceptorChain.pluginAll()
方法,对所有拦截器进行嵌套代理。
关键源码(InterceptorChain.java
):
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target); // 层层代理
}
return target;
}
3. Plugin 类的核心逻辑
Plugin
类通过动态代理(JDK Proxy)实现方法拦截:
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.invoke()
方法。 - 匹配拦截条件:检查调用的方法是否在
@Signature
注解中声明。
4. 拦截执行流程
代理对象方法调用时,会执行 Interceptor.intercept()
:
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);
}
}
三、注意事项
- 拦截器顺序:配置文件中声明的拦截器按照“外层代理”顺序执行,即后配置的拦截器先执行。
- 性能影响:过度使用拦截器会增加代理链长度,影响性能。
- 方法签名匹配:
@Signature
的type
、method
和args
必须与目标方法完全一致。 - 仅支持四大组件:只能拦截 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler。
四、总结
MyBatis 的 Plugin 机制通过动态代理和拦截器链实现 AOP 功能,是扩展 MyBatis 的核心手段。理解其源码有助于定制高级功能(如分页、加密等),但需谨慎避免滥用。可通过 Debug 跟踪 InterceptorChain
和 Plugin
类深入验证流程。