MyBatis的插件机制基于动态代理实现,允许开发者通过实现Interceptor
接口对ParameterHandler
、ResultSetHandler
、StatementHandler、Executor
这四大核心接口的方法进行拦截和增强。以下是其实现逻辑的详细解析:
一、插件实现的核心步骤
-
实现
Interceptor
接口
自定义插件需实现Interceptor
接口的三个方法:intercept()
:核心拦截逻辑,通过Invocation.proceed()
调用原始方法,可在方法执行前后插入自定义逻辑(如日志记录、参数修改等)。plugin()
:返回目标对象的代理对象,通常调用Plugin.wrap(target, this)
生成动态代理。setProperties()
:用于接收配置文件中的属性参数(可选)。
-
通过注解指定拦截目标
使用@Intercepts
和@Signature
注解标注需拦截的接口、方法及参数类型。例如:@Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) })
此处
type
指定拦截的接口(如Executor
),method
为方法名,args
通过参数列表区分重载方法。 -
注册插件到MyBatis配置
在mybatis-config.xml
中添加插件配置:<plugins> <plugin interceptor="com.example.MyInterceptor"> <property name="param" value="value"/> <!-- 可选属性配置 --> </plugin> </plugins>
或在Spring中通过Bean注册。
二、动态代理与拦截触发机制
-
代理对象生成
MyBatis在初始化时,通过InterceptorChain
遍历所有插件,依次调用plugin()
方法为目标对象(如Executor
)生成代理链。每个代理对象会包裹前一个代理,形成多层代理结构。 -
方法拦截流程
- 当调用代理对象的方法时,触发
InvocationHandler.invoke()
。 - 执行链式拦截器的
intercept()
方法,最终调用原始方法。 - 若拦截器未调用
Invocation.proceed()
,则后续拦截器和原始方法均不会执行。
- 当调用代理对象的方法时,触发
三、关键注意事项
-
拦截范围与顺序
- 拦截顺序取决于插件注册顺序,但不同接口的触发时机由MyBatis执行流程决定(如
Executor
早于StatementHandler
)。 - 仅拦截配置中明确声明的方法,例如
Executor.update()
仅拦截增删改操作。
- 拦截顺序取决于插件注册顺序,但不同接口的触发时机由MyBatis执行流程决定(如
-
性能与调试
- 频繁拦截或复杂逻辑可能影响性能,需谨慎设计。
- 多个插件嵌套时,需注意代理链的执行顺序和逻辑冲突。
四、示例场景
分页插件实现(参考PageHelper):
- 在
intercept()
中解析分页参数,重写SQL为LIMIT
语句。 - 通过
ThreadLocal
传递分页参数,避免线程安全问题。 - 拦截
Executor.query()
方法,在查询前修改SQL,查询后统计总数。
总结
MyBatis插件机制通过动态代理和拦截器链实现无侵入式扩展,开发者需关注接口方法签名、代理链生成及执行顺序。合理使用插件可增强框架功能(如日志、分页),但需权衡性能与复杂度。