前言在日常开发中我们经常使用MapperScan来自动扫描 MyBatis 的 Mapper 接口例如SpringBootApplicationMapperScan(com.demo.mapper)publicclassApp{}它可以自动将 Mapper 接口注册为 Spring Bean避免逐个编写MapperpublicinterfaceUserMapper{}很多人知道它“能用”但不了解它内部原理尤其是为什么 MyBatis-Spring 早期版本MapperScan是直接扫描 Mapper后续版本却改成先注册MapperScannerConfigurer再通过BeanDefinitionRegistryPostProcessor完成扫描本文就从源码角度详细分析新版MapperScan的执行原理与官方改造原因。一、MapperScan 注解入口分析先看MapperScan源码简化Retention(RetentionPolicy.RUNTIME)Target(ElementType.TYPE)DocumentedImport(MapperScannerRegistrar.class)publicinterfaceMapperScan{String[]basePackages()default{};}核心就一句Import(MapperScannerRegistrar.class)说明Spring 在启动解析配置类时会导入MapperScannerRegistrar。也就是说MapperScan ↓ Import ↓ MapperScannerRegistrar二、旧版本原理直接扫描早期版本MapperScannerRegistrar中逻辑比较直接publicvoidregisterBeanDefinitions(...){ClassPathMapperScannerscannernewClassPathMapperScanner(registry);scanner.registerFilters();scanner.scan(basePackages);}执行流程Spring启动 ↓ 解析MapperScan ↓ 执行MapperScannerRegistrar ↓ 立即扫描Mapper接口 ↓ 注册MapperFactoryBean例如publicinterfaceUserMapper{}会被注册成BeanNameuserMapper BeanClassMapperFactoryBean三、新版本原理延迟扫描后续版本官方改造后逻辑变成BeanDefinitionBuilderbuilderBeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);也就是说MapperScannerRegistrar不再直接扫描 Mapper而是先向 Spring 注册一个MapperScannerConfigurerBeanDefinition。1. 注册流程图MapperScan ↓ MapperScannerRegistrar ↓ 注册 MapperScannerConfigurer ↓ Spring 容器刷新 ↓ 执行 postProcessBeanDefinitionRegistry() ↓ 真正扫描 Mapper2. 关键源码MapperScannerRegistrarOverridepublicvoidregisterBeanDefinitions(...){BeanDefinitionBuilderbuilderBeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);builder.addPropertyValue(basePackage,com.demo.mapper);registry.registerBeanDefinition(mapperScannerConfigurer,builder.getBeanDefinition());}MapperScannerConfigurerpublicclassMapperScannerConfigurerimplementsBeanDefinitionRegistryPostProcessor{OverridepublicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistryregistry){ClassPathMapperScannerscannernewClassPathMapperScanner(registry);scanner.scan(basePackage);}}四、为什么官方要这样改造这是本文重点。五、原因一解决${}占位符问题最核心很多项目这样写MapperScan(${mybatis.mapper.package})配置文件mybatis:mapper:package:com.demo.mapper旧版本问题旧版本直接扫描时机太早ImportBeanDefinitionRegistrar 执行阶段此时 Spring 的EnvironmentPropertySourcesPlaceholder解析器可能还没准备完成。导致${mybatis.mapper.package}仍然是字符串本身无法解析。新版本优势改成MapperScannerConfigurerimplementsBeanDefinitionRegistryPostProcessor执行时机更晚Spring 容器 refresh 阶段此时application.yml 已加载占位符可解析Environment 就绪所以MapperScan(${xx})可以稳定运行。六、原因二更符合 Spring 生命周期设计Spring 提供专门扩展点BeanDefinitionRegistryPostProcessor作用在 Bean 实例化前动态注册 BeanDefinition。Mapper 扫描本质就是扫描接口 → 注册BeanDefinition所以放在这个阶段最合理。生命周期对比旧版Configuration解析阶段新版BeanDefinitionRegistryPostProcessor阶段显然新版更标准。七、原因三兼容更多配置项后续 MyBatis-Spring 支持很多参数MapperScan(lazyInitializationtrue,sqlSessionFactoryRefsqlSessionFactory,sqlSessionTemplateRefsqlSessionTemplate)如果直接扫描很多依赖Bean还没准备好例如SqlSessionFactory多数据源BeanNameGeneratorScope新架构先保存配置MapperScannerConfigurer等容器准备完成后再统一扫描兼容性更强。八、原因四统一 XML 与 注解实现早期 XML 写法beanclassorg.mybatis.spring.mapper.MapperScannerConfigurerpropertynamebasePackagevaluecom.demo.mapper//bean注解写法MapperScan(com.demo.mapper)如果两者底层逻辑不同维护成本高。新版统一为XML配置 ↓ MapperScannerConfigurer MapperScan ↓ MapperScannerConfigurer这样只维护一套扫描逻辑。九、最终执行流程新版完整链路1. Spring启动 2. 解析Configuration 3. 发现MapperScan 4. Import 导入 MapperScannerRegistrar 5. Registrar 注册 MapperScannerConfigurer 6. Spring refresh() 7. 执行 BeanDefinitionRegistryPostProcessor 8. MapperScannerConfigurer 开始扫描包路径 9. 找到 Mapper 接口 10. 注册 MapperFactoryBean 11. 注入成功十、Mapper 最终为什么能注入比如ResourceprivateUserMapperuserMapper;实际上 Spring 容器里并不是接口实例而是MapperFactoryBeanUserMapper它在getObject()时通过 MyBatis 动态代理生成sqlSession.getMapper(UserMapper.class)最终得到代理对象注入。十一、总结新版MapperScan的核心思想自己不扫描而是把扫描动作交给 Spring 生命周期更合适的阶段执行。一句话理解旧版MapperScan - 立刻扫描新版MapperScan - 注册扫描器Bean - 容器后期再扫描官方这样改造的根本原因支持${}占位符兼容 Spring Boot 自动配置支持多数据源等复杂场景统一 XML 与注解实现更符合 Spring 扩展规范十二、延伸思考高级开发者必懂为什么实现的是BeanDefinitionRegistryPostProcessor而不是BeanFactoryPostProcessor答案是因为 Mapper 扫描要“新增 BeanDefinition”而不是修改 Bean 实例。总结以上就是MapperScan新版本底层原理分析。表面看只是代码重构实际上体现了 Spring 框架级设计思想复杂功能不要抢跑交给正确生命周期执行。这也是很多优秀中间件MyBatis、Dubbo、Feign、Spring Cloud的共同设计理念。