全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-20 8 分钟 ✍️ juanwangdev

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 {
    // ...
}

四大对象可拦截方法速查

对象可拦截方法参数列表
ExecutorupdateMappedStatement, Object
ExecutorqueryMappedStatement, Object, RowBounds, ResultHandler
ExecutorqueryCursorMappedStatement, Object, RowBounds
ExecutorflushStatementsMappedStatement, Object
StatementHandlerprepareConnection, Integer
StatementHandlerparameterizeStatement
StatementHandlerqueryStatement, ResultHandler
StatementHandlerupdateStatement
StatementHandlerbatchStatement
ParameterHandlergetParameterObject
ParameterHandlersetParametersPreparedStatement
ResultSetHandlerhandleResultSetsStatement
ResultSetHandlerhandleOutputParametersCallableStatement

不是所有 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() 内部逻辑:

  1. 读取 @Intercepts 注解中的 @Signature 信息
  2. 判断目标对象类型是否在拦截范围内
  3. 匹配则创建 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;
    }
}

注意事项

  1. 方法签名精确匹配@Signatureargs 必须与目标方法签名完全一致,类型、数量、顺序均需匹配
  2. 必须实现 plugin():即使只写 return Plugin.wrap(target, this); 也必须实现,否则代理不会生效
  3. proceed() 调用:除非明确要短路(如命中缓存),否则必须调用 invocation.proceed() 保证原始方法执行
  4. 线程安全:插件实例是全局单例,setProperties() 只在初始化时调用一次,成员变量需保证线程安全
  5. 配置时机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

📝 发现内容有误?点击此处直接编辑

← 上一篇 自定义缓存实现
下一篇 → 分页插件原理
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库