在使用 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.
@Component及其衍生注解 (@Service,@Repository,@Controller): 通过@ComponentScan扫描,为标记的类创建 Bean。 - 2.
@Bean方法: 在@Configuration类中,方法的返回值被注册为 Bean。 - 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.
@MapperScan触发@Import:@MapperScan注解本身很简单,它最重要的作用是利用@Import(MapperScannerRegistrar.class)将MapperScannerRegistrar这个类引入到 Spring 的配置中。 - 2.
MapperScannerRegistrar注册“扫描器”:MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口。它的registerBeanDefinitions方法会被 Spring 在早期调用。它的主要工作不是直接扫描 Mapper 接口,而是注册一个更重要的 Bean ——MapperScannerConfigurer—— 的BeanDefinition,并将@MapperScan中指定的包路径等信息传递给它。 - 3.
MapperScannerConfigurer登场 (核心):MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口。这是真正的“魔法师” 。当 Spring 执行到BeanFactoryPostProcessor阶段时,会调用MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法。 - 4. 扫描与注册: 在
postProcessBeanDefinitionRegistry方法内部: -
- •
MapperScannerConfigurer使用传入的包路径,扫描 Classpath,找到所有符合条件的 Mapper 接口(如UserMapper,OrderMapper等)。 - • 对于每一个找到的接口,它并不会尝试去创建接口本身的 BeanDefinition,而是创建一个特殊的
BeanDefinition: -
- • 将
beanClass设置为org.mybatis.spring.mapper.MapperFactoryBean。 - • 将 Mapper 接口本身 (
UserMapper.class) 设置为MapperFactoryBean的一个属性(通常是mapperInterface属性)。
- • 将
- • 将这个
MapperFactoryBean的BeanDefinition注册到BeanDefinitionRegistry中,Bean 的名称通常就是接口名首字母小写(如userMapper)。
- •
- 5.
MapperFactoryBean创建代理: 当 Spring 容器后续需要创建名为userMapper的 Bean 时,它会: -
- • 根据注册的
BeanDefinition,实例化一个MapperFactoryBean对象。 - • 由于
MapperFactoryBean实现了 Spring 的FactoryBean接口,Spring 不会直接使用这个工厂 Bean,而是会调用其getObject()方法,期望它返回真正的UserMapperBean。 - • 在
getObject()方法内部,MapperFactoryBean会利用 MyBatis 的核心 API 和 JDK 动态代理,创建一个实现了UserMapper接口的代理对象。这个代理对象就是我们最终注入到 Service 中使用的那个 Bean。
- • 根据注册的
5. 总结
@MapperScan 的“魔法”并非凭空产生,而是建立在 Spring IoC 容器强大的扩展机制之上:
- •
BeanDefinitionRegistryPostProcessor提供了在容器启动早期动态注册 Bean 定义的能力,这是关键的“时机”和“入口”。 - •
MapperScannerConfigurer扮演了“扫描员”和“注册官”的角色,它利用上述能力,为每个 Mapper 接口注册了一个对应的“代理工厂” (MapperFactoryBean) 的定义。 - •
MapperFactoryBean则是最终的“工匠”,它在运行时利用动态代理技术,按需创建出 Mapper 接口的具体实现(代理对象)。