mapper接口注册为bean的魔法

5 阅读5分钟

在使用 MyBatis 配合 Spring Boot 时,我们都会习惯性地在启动类上加上 @MapperScan("com.example.mapper") 注解。然后,我们就能在 Service 层直接 @Autowired 注入 UserMapper 这样的接口,并调用其方法执行数据库操作。

但你是否思考过:

  • • UserMapper 只是一个接口,并没有具体的实现类,Spring 是如何为它创建 Bean 实例的?
  • • @MapperScan 这个注解背后,到底发生了什么?

许多开发者知道 MyBatis 用了动态代理,但却不清楚 Spring 是在哪个时机、通过什么机制来触发这个代理创建过程的。答案就隐藏在 Spring IoC 容器启动流程的一个核心扩展点——BeanDefinitionRegistryPostProcessor 之中。

1. 问题的提出:接口如何成为 Bean?

Spring IoC 容器管理的是 Bean 实例。通常,Spring 通过以下几种方式“发现”并创建 Bean:

  1. 1.  @Component 及其衍生注解 (@Service@Repository@Controller):  通过 @ComponentScan 扫描,为标记的创建 Bean。
  2. 2.  @Bean 方法:  在 @Configuration 类中,方法的返回值被注册为 Bean。
  3. 3. XML 配置:  <bean ...> 标签定义。

显然,MyBatis 的 Mapper 接口(如 UserMapper)不属于以上任何一种情况。它只是一个接口,没有实现类,也没有 @Bean 方法返回它的实例。那么,Spring 容器是如何知道要为 UserMapper 创建一个 Bean,并且这个 Bean 恰好是 MyBatis 需要的那个动态代理对象的呢?

答案:  这个 Bean 的“蓝图” (BeanDefinition),并非在标准的扫描或配置阶段被发现,而是由 MyBatis 的集成逻辑,在 Spring 容器启动的早期,动态地、按需地注册进去的。

2. Spring IoC 的“上帝之手”:BeanFactoryPostProcessor

在深入 BeanDefinitionRegistryPostProcessor 之前,我们先要了解它的“父级”——BeanFactoryPostProcessor

BeanFactoryPostProcessor 是 Spring IoC 容器提供的一个极其重要的扩展点。它允许我们在 Spring 已经加载完所有的 Bean 定义(BeanDefinition)之后,但在开始实例化任何 Bean 之前,对这些 BeanDefinition 的元数据进行修改

你可以把它想象成:在所有建筑蓝图都画好,但工人还没开始施工之前,允许一位总工程师对这些蓝图进行最后的审阅和调整。

典型的应用:

  • • 属性占位符替换:  PropertySourcesPlaceholderConfigurer 就是一个 BeanFactoryPostProcessor,它负责将 @Value("${...}") 中的占位符替换为真实的配置值。
  • • 修改 Bean 作用域、添加依赖等。

局限性:  BeanFactoryPostProcessor 只能修改已存在的 BeanDefinition,它不能添加新的 BeanDefinition

3. “创世之力”:BeanDefinitionRegistryPostProcessor

而 @MapperScan 的需求,恰恰是要凭空创造出 UserMapper 等接口对应的 BeanDefinition。这时,就需要 BeanFactoryPostProcessor 的一个特殊子接口登场了——BeanDefinitionRegistryPostProcessor

核心能力:
BeanDefinitionRegistryPostProcessor 不仅继承了 BeanFactoryPostProcessor 的所有能力,还额外获得了一个关键的“特权”:它可以在更早的阶段(甚至在其他 BeanFactoryPostProcessor 执行之前)介入,并且可以直接访问 BeanDefinitionRegistry 这个负责注册 Bean 定义的核心组件。

关键方法:

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

postProcessBeanDefinitionRegistry 方法接收一个 BeanDefinitionRegistry 对象,允许你:

  • • 注册新的 BeanDefinition:  registry.registerBeanDefinition(...)
  • • 移除已有的 BeanDefinition:  registry.removeBeanDefinition(...)
  • • 检查是否已存在某个 BeanDefinition:  registry.containsBeanDefinition(...)

这正是 MyBatis 所需要的“创世之力”!

4. @MapperScan 的“三步走”工作流程

现在,我们可以完整地串联起 @MapperScan 的工作流程了。

流程图 (简化版):

详细步骤解析:

  1. 1.  @MapperScan 触发 @Import:  @MapperScan 注解本身很简单,它最重要的作用是利用 @Import(MapperScannerRegistrar.class) 将 MapperScannerRegistrar 这个类引入到 Spring 的配置中。
  2. 2. MapperScannerRegistrar 注册“扫描器”:  MapperScannerRegistrar 实现了 ImportBeanDefinitionRegistrar 接口。它的 registerBeanDefinitions 方法会被 Spring 在早期调用。它的主要工作不是直接扫描 Mapper 接口,而是注册一个更重要的 Bean —— MapperScannerConfigurer —— 的 BeanDefinition,并将 @MapperScan 中指定的包路径等信息传递给它。
  3. 3. MapperScannerConfigurer 登场 (核心):  MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口。这是真正的“魔法师” 。当 Spring 执行到 BeanFactoryPostProcessor 阶段时,会调用 MapperScannerConfigurer 的 postProcessBeanDefinitionRegistry 方法。
  4. 4. 扫描与注册:  在 postProcessBeanDefinitionRegistry 方法内部:
    • • MapperScannerConfigurer 使用传入的包路径,扫描 Classpath,找到所有符合条件的 Mapper 接口(如 UserMapperOrderMapper 等)。
    • • 对于每一个找到的接口,它并不会尝试去创建接口本身的 BeanDefinition,而是创建一个特殊的 BeanDefinition
      • • 将 beanClass 设置为 org.mybatis.spring.mapper.MapperFactoryBean
      • • 将 Mapper 接口本身 (UserMapper.class) 设置为 MapperFactoryBean 的一个属性(通常是 mapperInterface 属性)。
    • • 将这个 MapperFactoryBean 的 BeanDefinition 注册到 BeanDefinitionRegistry 中,Bean 的名称通常就是接口名首字母小写(如 userMapper)。
  5. 5. MapperFactoryBean 创建代理:  当 Spring 容器后续需要创建名为 userMapper 的 Bean 时,它会:
    • • 根据注册的 BeanDefinition,实例化一个 MapperFactoryBean 对象。
    • • 由于 MapperFactoryBean 实现了 Spring 的 FactoryBean 接口,Spring 不会直接使用这个工厂 Bean,而是会调用其 getObject() 方法,期望它返回真正的 UserMapper Bean。
    • • 在 getObject() 方法内部,MapperFactoryBean 会利用 MyBatis 的核心 API 和 JDK 动态代理,创建一个实现了 UserMapper 接口的代理对象。这个代理对象就是我们最终注入到 Service 中使用的那个 Bean。

5. 总结

@MapperScan 的“魔法”并非凭空产生,而是建立在 Spring IoC 容器强大的扩展机制之上:

  • • BeanDefinitionRegistryPostProcessor 提供了在容器启动早期动态注册 Bean 定义的能力,这是关键的“时机”和“入口”。
  • • MapperScannerConfigurer 扮演了“扫描员”和“注册官”的角色,它利用上述能力,为每个 Mapper 接口注册了一个对应的“代理工厂” (MapperFactoryBean) 的定义。
  • • MapperFactoryBean 则是最终的“工匠”,它在运行时利用动态代理技术,按需创建出 Mapper 接口的具体实现(代理对象)。