MyBatis 的插件机制是其核心扩展能力之一,通过动态代理和拦截器模型,允许开发者在 SQL 执行的关键节点插入自定义逻辑(如日志记录、性能监控、SQL 改写等)。以下是其核心原理和实现细节的深度解析:
一、插件运行原理的核心架构
-
拦截器接口(Interceptor)
• 定义:插件需实现Interceptor接口,包含三个核心方法:◦intercept(Invocation invocation):拦截目标方法,执行自定义逻辑。
◦plugin(Object target):生成代理对象,包装目标对象。
◦setProperties(Properties properties):设置插件属性(通过 XML 配置传递参数)。
• 作用:拦截器是插件的核心逻辑载体,决定何时触发拦截及如何处理。
-
动态代理机制
• 代理对象生成:MyBatis 通过 Java 动态代理(Proxy.newProxyInstance)为被拦截的核心组件(如Executor、StatementHandler)生成代理类。• 方法拦截:当调用代理对象的方法时,会触发InvocationHandler的invoke方法,进而执行插件的intercept方法。
- 插件链(Interceptor Chain)
• 多插件协作:多个插件可按配置顺序拦截同一方法,形成链式调用。每个插件的intercept方法依次执行,最终调用invocation.proceed()传递到下一个插件或原始方法。
二、插件执行流程详解
-
插件注册
• 配置方式:在mybatis-config.xml中通过<plugins>标签注册插件,或使用@Intercepts注解声明拦截目标。• 示例配置:
1
2
3
4
5
<plugins><plugininterceptor="com.example.LoggingInterceptor"><propertyname="logLevel"value="DEBUG"/></plugin></plugins> -
目标对象包装
• 代理生成时机:MyBatis 初始化时扫描所有插件,根据@Signature注解匹配目标类和方法,生成代理对象。• 核心组件拦截:插件可作用于以下四个核心接口:
◦Executor:SQL 执行器(如update、query方法)。
◦StatementHandler:SQL 语句处理器(如预编译、参数绑定)。
◦ParameterHandler:参数处理器(参数设置)。
◦ResultSetHandler:结果集处理器(结果映射)。
-
方法拦截与逻辑增强
• 拦截点选择:通过@Signature注解指定拦截目标:1
2
3
@Intercepts({@Signature(type = Executor.class, method ="update", args = {MappedStatement.class, Object.class})})• 逻辑插入:在intercept方法中,可通过invocation.getArgs()获取方法参数,通过invocation.proceed()调用原始方法,实现日志记录、SQL 改写等操作。
三、插件开发示例
-
日志记录插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Intercepts({@Signature(type = Executor.class, method ="update", args = {MappedStatement.class, Object.class})})publicclassSqlLogInterceptorimplementsInterceptor {@OverridepublicObject intercept(Invocation invocation)throwsThrowable {MappedStatement ms = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];String sql = ms.getBoundSql(parameter).getSql();System.out.println("Executing SQL: "+ sql);longstart = System.currentTimeMillis();Object result = invocation.proceed();System.out.println("Execution time: "+ (System.currentTimeMillis() - start) +"ms");returnresult;}@OverridepublicObject plugin(Object target) {returnPlugin.wrap(target,this);}} -
分页插件(简化版)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Intercepts({@Signature(type = StatementHandler.class, method ="prepare", args = {Connection.class, Integer.class})})publicclassPaginationInterceptorimplementsInterceptor {@OverridepublicObject intercept(Invocation invocation)throwsThrowable {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 中的 SQLField field = boundSql.getClass().getDeclaredField("sql");field.setAccessible(true);field.set(boundSql, pageSql);returninvocation.proceed();}}
四、插件应用场景
| 场景 | 实现方式 | 典型插件 |
|---|---|---|
| SQL 日志记录 | 拦截Executor.update和query方法 | p6spy、MyBatis-Log4j |
| 分页查询 | 拦截StatementHandler.prepare方法 | PageHelper |
| 性能监控 | 统计 SQL 执行时间 | MyBatis-Performance |
| 数据加密/解密 | 拦截ParameterHandler.setParameters | Jasypt-MyBatis |
| 自动事务管理 | 拦截Executor.commit/rollback方法 | 自定义事务插件 |
2142

被折叠的 条评论
为什么被折叠?



