news 2026/4/27 8:41:47

浅谈MapperScan

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
浅谈MapperScan

前言

在日常开发中,我们经常使用@MapperScan来自动扫描 MyBatis 的 Mapper 接口,例如:

@SpringBootApplication@MapperScan("com.demo.mapper")publicclassApp{}

它可以自动将 Mapper 接口注册为 Spring Bean,避免逐个编写:

@MapperpublicinterfaceUserMapper{}

很多人知道它“能用”,但不了解它内部原理,尤其是:

为什么 MyBatis-Spring 早期版本@MapperScan是直接扫描 Mapper,后续版本却改成先注册MapperScannerConfigurer,再通过BeanDefinitionRegistryPostProcessor完成扫描?

本文就从源码角度,详细分析新版@MapperScan的执行原理与官方改造原因。


一、@MapperScan 注解入口分析

先看@MapperScan源码(简化):

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(MapperScannerRegistrar.class)public@interfaceMapperScan{String[]basePackages()default{};}

核心就一句:

@Import(MapperScannerRegistrar.class)

说明:

Spring 在启动解析配置类时,会导入MapperScannerRegistrar

也就是说:

@MapperScan ↓ @Import ↓ MapperScannerRegistrar

二、旧版本原理(直接扫描)

早期版本MapperScannerRegistrar中逻辑比较直接:

publicvoidregisterBeanDefinitions(...){ClassPathMapperScannerscanner=newClassPathMapperScanner(registry);scanner.registerFilters();scanner.scan(basePackages);}

执行流程:

Spring启动 ↓ 解析@MapperScan ↓ 执行MapperScannerRegistrar ↓ 立即扫描Mapper接口 ↓ 注册MapperFactoryBean

例如:

publicinterfaceUserMapper{}

会被注册成:

BeanName=userMapper BeanClass=MapperFactoryBean

三、新版本原理(延迟扫描)

后续版本官方改造后,逻辑变成:

BeanDefinitionBuilderbuilder=BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);

也就是说:

MapperScannerRegistrar不再直接扫描 Mapper,而是先向 Spring 注册一个MapperScannerConfigurerBeanDefinition。


1. 注册流程图

@MapperScan ↓ MapperScannerRegistrar ↓ 注册 MapperScannerConfigurer ↓ Spring 容器刷新 ↓ 执行 postProcessBeanDefinitionRegistry() ↓ 真正扫描 Mapper

2. 关键源码

MapperScannerRegistrar

@OverridepublicvoidregisterBeanDefinitions(...){BeanDefinitionBuilderbuilder=BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);builder.addPropertyValue("basePackage","com.demo.mapper");registry.registerBeanDefinition("mapperScannerConfigurer",builder.getBeanDefinition());}

MapperScannerConfigurer

publicclassMapperScannerConfigurerimplementsBeanDefinitionRegistryPostProcessor{@OverridepublicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistryregistry){ClassPathMapperScannerscanner=newClassPathMapperScanner(registry);scanner.scan(basePackage);}}

四、为什么官方要这样改造?

这是本文重点。


五、原因一:解决${}占位符问题(最核心)

很多项目这样写:

@MapperScan("${mybatis.mapper.package}")

配置文件:

mybatis:mapper:package:com.demo.mapper

旧版本问题

旧版本直接扫描时机太早:

ImportBeanDefinitionRegistrar 执行阶段

此时 Spring 的:

  • Environment
  • PropertySources
  • Placeholder解析器

可能还没准备完成。

导致:

"${mybatis.mapper.package}"

仍然是字符串本身,无法解析。


新版本优势

改成:

MapperScannerConfigurerimplementsBeanDefinitionRegistryPostProcessor

执行时机更晚:

Spring 容器 refresh 阶段

此时:

  • application.yml 已加载
  • 占位符可解析
  • Environment 就绪

所以:

@MapperScan("${xx}")

可以稳定运行。


六、原因二:更符合 Spring 生命周期设计

Spring 提供专门扩展点:

BeanDefinitionRegistryPostProcessor

作用:

在 Bean 实例化前,动态注册 BeanDefinition。

Mapper 扫描本质就是:

扫描接口 → 注册BeanDefinition

所以放在这个阶段最合理。


生命周期对比

旧版:

@Configuration解析阶段

新版:

BeanDefinitionRegistryPostProcessor阶段

显然新版更标准。


七、原因三:兼容更多配置项

后续 MyBatis-Spring 支持很多参数:

@MapperScan(lazyInitialization="true",sqlSessionFactoryRef="sqlSessionFactory",sqlSessionTemplateRef="sqlSessionTemplate")

如果直接扫描:

很多依赖Bean还没准备好

例如:

  • SqlSessionFactory
  • 多数据源
  • BeanNameGenerator
  • Scope

新架构先保存配置:

MapperScannerConfigurer

等容器准备完成后再统一扫描,兼容性更强。


八、原因四:统一 XML 与 注解实现

早期 XML 写法:

<beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer"><propertyname="basePackage"value="com.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 容器里并不是接口实例,而是:

MapperFactoryBean<UserMapper>

它在getObject()时通过 MyBatis 动态代理生成:

sqlSession.getMapper(UserMapper.class)

最终得到代理对象注入。


十一、总结

新版@MapperScan的核心思想:

自己不扫描,而是把扫描动作交给 Spring 生命周期更合适的阶段执行。


一句话理解

旧版:

@MapperScan -> 立刻扫描

新版:

@MapperScan -> 注册扫描器Bean -> 容器后期再扫描

官方这样改造的根本原因

  1. 支持${}占位符
  2. 兼容 Spring Boot 自动配置
  3. 支持多数据源等复杂场景
  4. 统一 XML 与注解实现
  5. 更符合 Spring 扩展规范

十二、延伸思考(高级开发者必懂)

为什么实现的是:

BeanDefinitionRegistryPostProcessor

而不是:

BeanFactoryPostProcessor

答案是:

因为 Mapper 扫描要“新增 BeanDefinition”,而不是修改 Bean 实例。


总结

以上就是@MapperScan新版本底层原理分析。

表面看只是代码重构,实际上体现了 Spring 框架级设计思想:

复杂功能不要抢跑,交给正确生命周期执行。

这也是很多优秀中间件(MyBatis、Dubbo、Feign、Spring Cloud)的共同设计理念。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/27 8:34:43

3分钟快速上手:baidupankey百度网盘提取码智能查询终极指南

3分钟快速上手&#xff1a;baidupankey百度网盘提取码智能查询终极指南 【免费下载链接】baidupankey 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 还在为百度网盘分享链接的提取码而烦恼吗&#xff1f;每次遇到需要密码的资源都要四处搜索&#xff0c;浪…

作者头像 李华
网站建设 2026/4/27 8:24:44

Phi-4-mini-reasoning部署教程(百度搜索TOP10):轻量级推理模型首选

Phi-4-mini-reasoning部署教程&#xff08;百度搜索TOP10&#xff09;&#xff1a;轻量级推理模型首选 1. 项目概述 Phi-4-mini-reasoning是微软推出的3.8B参数轻量级开源模型&#xff0c;专为数学推理、逻辑推导和多步解题等强逻辑任务设计。这个模型主打"小参数、强推…

作者头像 李华
网站建设 2026/4/27 8:24:10

动态规划专题(10):最优三角剖分问题

2026.04.061. 问题定义最优三角剖分&#xff08;Optimal Triangulation&#xff09;问题是指&#xff1a;给定一个凸多边形&#xff0c;将其划分成若干个不相交的三角形&#xff0c;使得这些三角形的某种权值之和最小。有一块多边形的披萨饼&#xff0c;上面有很多蔬菜和肉片&a…

作者头像 李华
网站建设 2026/4/27 8:22:40

GHelper完整指南:华硕笔记本终极性能优化免费教程

GHelper完整指南&#xff1a;华硕笔记本终极性能优化免费教程 【免费下载链接】g-helper Lightweight, open-source control tool for ASUS laptops and ROG Ally. Manage performance modes, fans, GPU, battery, and RGB lighting across Zephyrus, Flow, TUF, Strix, Scar, …

作者头像 李华
网站建设 2026/4/27 8:21:36

MySQL:Fuzzy Checkpoint

一、 为什么需要“模糊&#xff08;Fuzzy&#xff09;”&#xff1f;对比 Sharp Checkpoint Sharp Checkpoint&#xff08;全量检查点&#xff09;&#xff1a; 顾名思义&#xff0c;要求将 Buffer Pool 中所有的脏页一次性全部刷新到磁盘。 触发时机&#xff1a; 通常只在数据…

作者头像 李华