[019][数据模块]MyBatis-Plus 拦截器扩展设计:基于函数式接口与 Spring 自动装配

019数据模块MyBatis-Plus 拦截器扩展设计:基于函数式接口与 Spring 自动装配

本项目代码:gitee.com/yunjiao-sou...

在基于 MyBatis-Plus 的数据访问层开发中,拦截器(Interceptor)是扩展分页、乐观锁、防全表误操作等能力的核心组件。通常我们会在配置类中手动创建 MybatisPlusInterceptor 并添加各个 InnerInterceptor。这种方式虽然直观,但当需要支持模块化、按顺序注册、允许外部定制时,就显得不够灵活。

本文介绍一种更优雅的设计:通过函数式接口 InnerInterceptorSupplier + Spring 的 ObjectProvider 机制,实现拦截器的自动收集与顺序装配 。并结合 DataMybatisPlusConfiguration 的示例代码,分析其设计思想与实战用法。


一、背景:MyBatis-Plus 拦截器体系

MyBatis-Plus 提供了一套基于 Interceptor 的插件机制,其中 MybatisPlusInterceptor 是责任链的核心,它可以添加多个 InnerInterceptor(如分页、乐观锁、防全表攻击等)。典型配置如下:

java 复制代码
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
    return interceptor;
}

这种写法的缺点:

  • 如果是框架或 starter 项目,用户想追加 自定义拦截器,只能通过覆盖 MybatisPlusInterceptor Bean 的方式,容易丢失默认拦截器。
  • 拦截器的顺序不易被外部控制。
  • 不够"声明式",缺乏扩展点。

二、核心接口:InnerInterceptorSupplier

java 复制代码
@FunctionalInterface
public interface InnerInterceptorSupplier extends Supplier<InnerInterceptor> {
}

这是一个极其简洁的函数式接口,继承自 Supplier<InnerInterceptor>。它的作用不是直接提供拦截器,而是声明一个能够提供拦截器的工厂

在 Spring 容器中,任何实现了该接口的 Bean 都会被识别,并用于向 MybatisPlusInterceptor 贡献拦截器实例。这样做的好处:

  • 延迟创建Supplier 允许在真正需要时才 get() 拦截器。
  • 允许带参数的构造 :比如分页拦截器需要 DbType,可以在 Supplier 实现中从配置中读取。
  • 支持 @Order 排序:多个 Supplier Bean 可以定义顺序,最终拦截器链的顺序就是这些 Supplier 的执行顺序。

三、自动配置类:DataMybatisPlusConfiguration

3.1 核心 Bean:mybatisPlusInterceptor

java 复制代码
@Bean
MybatisPlusInterceptor mybatisPlusInterceptor(ObjectProvider<InnerInterceptorSupplier> innerInterceptorSuppliers) {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    innerInterceptorSuppliers.orderedStream()
            .map(InnerInterceptorSupplier::get)
            .forEach(interceptor::addInnerInterceptor);
    return interceptor;
}

关键点解析:

  • ObjectProvider<InnerInterceptorSupplier>:Spring 提供的依赖注入方式,能获取指定类型的所有 Bean,并支持有序流(orderedStream())。
  • orderedStream() 会按照每个 Bean 上的 @Order 注解或 Ordered 接口进行排序。
  • 依次调用 get() 获取拦截器实例,添加到 MybatisPlusInterceptor 中。

这种设计使得拦截器的增加完全基于容器中的 Supplier Bean ,而不是硬编码。外部模块只需要定义自己的 InnerInterceptorSupplier Bean 即可自动生效。

3.2 默认注册的三个内置拦截器

配置类通过 @Bean 方法提供了三个预置的 Supplier,并指定了顺序:

顺序 拦截器类型 作用
100 PaginationInnerInterceptor 自动分页,根据数据库方言生成分页 SQL
200 OptimisticLockerInnerInterceptor 乐观锁,配合 @Version 字段使用
300 BlockAttackInnerInterceptor 防全表更新/删除,防止无 where 条件的 SQL

代码示例:

java 复制代码
@Bean
@Order(100)
InnerInterceptorSupplier paginationInnerInterceptorSupplier(DataProperties properties) {
    return () -> new PaginationInnerInterceptor(properties.getMybatisPlus().getDbType());
}

注意分页拦截器需要从配置类 DataProperties 中获取数据库类型,这正是使用了 Supplier 的延迟构造能力。


四、如何扩展自定义拦截器?

假设我们需要添加一个 SQL 性能监控拦截器(自定义实现 InnerInterceptor),步骤如下:

  1. 实现自定义拦截器(例如 PerformanceInnerInterceptor)。
  2. 在任意 @Configuration 类中声明一个 InnerInterceptorSupplier Bean,并指定 @Order(决定它在拦截器链中的位置)。
java 复制代码
@Configuration
public class MyCustomInterceptorConfig {

    @Bean
    @Order(50)  // 将在分页拦截器(Order=100)之前执行
    public InnerInterceptorSupplier performanceInterceptorSupplier() {
        // 如果拦截器需要其他依赖,可以在这里从方法参数传入
        return PerformanceInnerInterceptor::new;
    }
}

启动 Spring Boot 应用后,DataMybatisPlusConfiguration 会自动收集该 Supplier 并将其拦截器添加到链中。

如果想替换 某个默认拦截器(比如修改分页拦截器的参数),无需覆盖整个 MybatisPlusInterceptor,只需提供一个更高优先级@Order 更小)的同类型 Supplier 会怎么样?实际上不会替换,而是增加。若要真正的替换,需要小心设计:最好在配置类中通过 @ConditionalOnMissingBean 来让默认 Supplier 有条件注册。不过当前设计下,默认 Supplier 总是注册,所以用户如果想完全自定义分页逻辑,可以提供一个相同类型的拦截器(但顺序可能需要在默认之前或之后,取决于业务需要)。更标准的做法是让默认的核心拦截器也支持条件化,但本文不展开。


五、设计优势总结

设计点 传统方式 本文方式
扩展性 覆盖整个 MybatisPlusInterceptor Bean 只需添加一个 InnerInterceptorSupplier Bean
顺序控制 硬编码 addInnerInterceptor 顺序 @Order 注解声明式排序
延迟创建 Supplier 支持带参或条件化创建
框架集成友好 较差 符合 Spring 自动装配哲学,适合 Starter 开发

六、潜在注意点

  1. 顺序一致性问题orderedStream() 依赖 @Order,但不同 Supplier 之间若无明确顺序,行为可能不确定。建议所有自定义 Supplier 都显式标注 @Order
  2. 拦截器生效范围MybatisPlusInterceptor 对 MyBatis-Plus 的所有方法都生效,但部分拦截器(如分页)只对特定执行器有效,这是 MyBatis-Plus 自身行为。
  3. 重复添加风险:若多个 Supplier 返回同一类型的拦截器(比如两个分页拦截器),可能会产生冲突。通常默认配置已足够,扩展时应注意避免重复。
  4. 性能开销:Supplier 的调用仅在容器启动且装配拦截器时执行一次,运行时无额外开销。

七、总结

InnerInterceptorSupplier + DataMybatisPlusConfiguration 的设计展现了一种面向扩展开放,面向修改关闭 的优雅实践。它充分利用了 Java 8 的 Supplier 函数式接口、Spring 的 ObjectProvider 有序注入以及 @Order 排序机制,使得 MyBatis-Plus 拦截器链的构建变得高度可插拔、可排序、可配置。

如果你的项目中也需要封装 MyBatis-Plus 基础设施,或者开发一个数据访问 Starter,这种模式值得借鉴。完整的代码已在 tutorials4j 框架中实现,读者可根据自身需求裁剪或增强。

相关推荐
星辰徐哥2 小时前
Spring Boot 微服务架构设计与实现
spring boot·后端·微服务
星辰徐哥2 小时前
Spring Boot 数据导入导出与报表生成
spring boot·后端·ui
明夜之约2 小时前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee2 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Micro麦可乐2 小时前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统
Jinkxs2 小时前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端
毕设源码_郑学姐2 小时前
计算机毕业设计springboot网络相册设计与实现 基于Spring Boot框架的在线相册管理系统开发与应用 Spring Boot驱动的网络影集设计与实践
spring boot·后端·课程设计
辣机小司2 小时前
【踩坑记录:Spring Boot 配置文件读取值不一致?警惕 YAML 的“八进制陷阱”与 SnakeYAML 版本之谜】
java·spring boot·后端·yaml·踩坑记录
一条小锦吕*3 小时前
基于Spring Boot + 数据可视化 + 协同过滤算法的推荐系统设计与实现(源码+论文+部署全讲解)
spring boot·算法·信息可视化
Jinkxs3 小时前
Prometheus - 监控微服务:Spring Boot 应用指标暴露与监控
spring boot·微服务·prometheus