Interceptor 接口实现
MyBatis 插件只需实现 Interceptor 接口,通过 @Intercepts 和 @Signature 注解声明拦截目标,配合三个核心方法完成插件开发。
Interceptor 接口定义
Java
public interface Interceptor {
/**
* 拦截方法核心逻辑
* @param invocation 封装了目标对象、目标方法和参数
* @return 方法执行结果
*/
Object intercept(Invocation invocation) throws Throwable;
/**
* 为目标对象创建代理
* @param target 被拦截的目标对象
* @return 代理对象或原对象
*/
Object plugin(Object target);
/**
* 设置插件属性(从 XML 配置读取)
* @param properties 配置参数
*/
void setProperties(Properties properties);
}
三个方法各司其职:
intercept():核心逻辑,在目标方法执行前后注入自定义行为plugin():代理创建,决定是否为目标对象创建代理setProperties():配置初始化,读取 XML 中的<property>参数
@Intercepts 与 @Signature 注解
注解定义
Java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
Signature[] value(); // 可声明多个 @Signature
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
Class<?> type(); // 拦截的对象类型
String method(); // 拦截的方法名
Class<?>[] args(); // 方法参数类型列表
}
声明拦截点
Java
@Intercepts({
// 拦截 Executor.query() 方法
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
),
// 拦截 Executor.update() 方法
@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
)
})
public class MyInterceptor implements Interceptor {
// ...
}
四大对象可拦截方法速查
| 对象 | 可拦截方法 | 参数列表 |
|---|---|---|
Executor | update | MappedStatement, Object |
Executor | query | MappedStatement, Object, RowBounds, ResultHandler |
Executor | queryCursor | MappedStatement, Object, RowBounds |
Executor | flushStatements | MappedStatement, Object |
StatementHandler | prepare | Connection, Integer |
StatementHandler | parameterize | Statement |
StatementHandler | query | Statement, ResultHandler |
StatementHandler | update | Statement |
StatementHandler | batch | Statement |
ParameterHandler | getParameterObject | 无 |
ParameterHandler | setParameters | PreparedStatement |
ResultSetHandler | handleResultSets | Statement |
ResultSetHandler | handleOutputParameters | CallableStatement |
不是所有 public 方法都可拦截,只有上述列出的方法被 MyBatis 签名匹配机制支持。
三个核心方法实现
1. intercept() — 拦截逻辑
Java
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取目标对象
Object target = invocation.getTarget();
// 获取目标方法
Method method = invocation.getMethod();
// 获取方法参数
Object[] args = invocation.getArgs();
// --- 前置处理 ---
long startTime = System.currentTimeMillis();
String methodName = method.getName();
System.out.println("Before: " + methodName);
// --- 执行原始方法 ---
Object result = invocation.proceed();
// --- 后置处理 ---
long elapsed = System.currentTimeMillis() - startTime;
System.out.println("After: " + methodName + " cost " + elapsed + "ms");
return result;
}
关键方法说明:
| Invocation 方法 | 返回值 | 说明 |
|---|---|---|
getTarget() | Object | 被代理的目标对象 |
getMethod() | Method | 当前被拦截的方法 |
getArgs() | Object[] | 方法调用参数 |
proceed() | Object | 执行下一个拦截器或原始方法 |
2. plugin() — 代理创建
Java
@Override
public Object plugin(Object target) {
// 标准写法:使用 Plugin.wrap() 创建代理
return Plugin.wrap(target, this);
}
Plugin.wrap() 内部逻辑:
- 读取
@Intercepts注解中的@Signature信息 - 判断目标对象类型是否在拦截范围内
- 匹配则创建 JDK 动态代理,否则返回原对象
可直接写
return Plugin.wrap(target, this);,无需手动判断类型,Plugin.wrap()已内置匹配逻辑。
3. setProperties() — 配置初始化
Java
@Override
public void setProperties(Properties properties) {
// 读取配置,设置插件参数
String dialect = properties.getProperty("dialect", "mysql");
String maxRows = properties.getProperty("maxRows", "1000");
System.out.println("Plugin initialized: dialect=" + dialect + ", maxRows=" + maxRows);
}
完整插件示例
SQL 打印插件
Java
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare",
args = {Connection.class, Integer.class})
})
public class SqlPrintInterceptor implements Interceptor {
private boolean prettyPrint = false;
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler handler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
// 打印 SQL
System.out.println("=== SQL ===");
if (prettyPrint) {
System.out.println(formatSql(sql));
} else {
System.out.println(sql);
}
// 打印参数
if (parameterObject != null) {
System.out.println("Parameters: " + parameterObject);
}
System.out.println("=============");
// 继续执行
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties props) {
this.prettyPrint = Boolean.parseBoolean(props.getProperty("prettyPrint", "false"));
}
// 简单 SQL 格式化
private String formatSql(String sql) {
return sql.replaceAll("(?i)\\bSELECT\\b", "\nSELECT")
.replaceAll("(?i)\\bFROM\\b", "\nFROM")
.replaceAll("(?i)\\bWHERE\\b", "\nWHERE")
.replaceAll("(?i)\\bORDER BY\\b", "\nORDER BY")
.replaceAll("(?i)\\bGROUP BY\\b", "\nGROUP BY")
.replaceAll("(?i)\\bJOIN\\b", "\nJOIN")
.replaceAll("(?i)\\bON\\b", "\n ON")
.trim();
}
}
参数加密插件
Java
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters",
args = {PreparedStatement.class})
})
public class EncryptParameterInterceptor implements Interceptor {
private String encryptFields = ""; // 需要加密的字段列表
private Set<String> fieldSet = new HashSet<>();
@Override
public Object intercept(Invocation invocation) throws Throwable {
ParameterHandler handler = (ParameterHandler) invocation.getTarget();
// 通过反射获取 ParameterObject 和 BoundSql
MetaObject metaObject = SystemMetaObject.forObject(handler);
Object parameterObject = metaObject.getValue("parameterObject");
BoundSql boundSql = (BoundSql) metaObject.getValue("boundSql");
// 加密指定字段
if (parameterObject instanceof Map) {
Map<String, Object> paramMap = (Map<String, Object>) parameterObject;
for (String field : fieldSet) {
if (paramMap.containsKey(field)) {
Object value = paramMap.get(field);
if (value instanceof String) {
paramMap.put(field, encrypt((String) value));
}
}
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties props) {
this.encryptFields = props.getProperty("fields", "");
this.fieldSet = new HashSet<>(Arrays.asList(this.encryptFields.split(",")));
}
private String encrypt(String value) {
// 实际应使用 AES/RSA 等加密算法
return "ENC(" + value + ")";
}
}
XML 配置
XML
<!-- mybatis-config.xml -->
<plugins>
<!-- SQL 打印插件 -->
<plugin interceptor="com.example.SqlPrintInterceptor">
<property name="prettyPrint" value="true"/>
</plugin>
<!-- 参数加密插件 -->
<plugin interceptor="com.example.EncryptParameterInterceptor">
<property name="fields" value="idCard,phone,email"/>
</plugin>
</plugins>
Java Config 方式
Java
// Spring Boot 配置
@Configuration
public class MyBatisConfig {
@Bean
public SqlPrintInterceptor sqlPrintInterceptor() {
SqlPrintInterceptor interceptor = new SqlPrintInterceptor();
Properties props = new Properties();
props.setProperty("prettyPrint", "true");
interceptor.setProperties(props);
return interceptor;
}
@Bean
public EncryptParameterInterceptor encryptInterceptor() {
EncryptParameterInterceptor interceptor = new EncryptParameterInterceptor();
Properties props = new Properties();
props.setProperty("fields", "idCard,phone,email");
interceptor.setProperties(props);
return interceptor;
}
}
注意事项
- 方法签名精确匹配:
@Signature的args必须与目标方法签名完全一致,类型、数量、顺序均需匹配- 必须实现 plugin():即使只写
return Plugin.wrap(target, this);也必须实现,否则代理不会生效- proceed() 调用:除非明确要短路(如命中缓存),否则必须调用
invocation.proceed()保证原始方法执行- 线程安全:插件实例是全局单例,
setProperties()只在初始化时调用一次,成员变量需保证线程安全- 配置时机:
setProperties()在plugin()之前调用,可在plugin()中使用已初始化的配置
要点总结
Interceptor接口包含三个方法:intercept()核心逻辑、plugin()代理创建、setProperties()配置初始化@Intercepts可声明多个@Signature,每个指定拦截对象类型、方法名和参数列表- 四大对象共支持约 13 个可拦截方法,不是所有 public 方法都可拦截
Plugin.wrap(target, this)内置类型匹配逻辑,不匹配时返回原对象,匹配时创建 JDK 动态代理- 插件实例为全局单例,成员变量需线程安全,
setProperties()只在初始化时调用一次 - 可通过 XML
<plugin>或 Spring@Bean方式注册插件,<property>参数通过setProperties()注入
存放路径:D:\git2\jwdev\articles\MYBATIS\进阶\插件机制\Interceptor 接口实现.md
📝 发现内容有误?点击此处直接编辑