15.Mybatis之特殊的BeanDefinition
1.前置配置
<!-- 触发 BeanDefinition 创建的核心配置 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/> <!-- 扫描包 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <!-- 关联SqlSessionFactory -->
</bean>
2.Spring 容器初始化时,会触发 MapperScannerConfigurer 执行,Spring 容器启动时会调用其 postProcessBeanDefinitionRegistry 方法 —— 这是 BeanDefinition 创建的入口
// MapperScannerConfigurer 核心入口方法
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 1. 初始化扫描器(绑定Spring的BeanDefinition注册表)
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 2. 配置扫描器(关联SqlSessionFactory、设置过滤规则等)
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setAnnotationClass(Mapper.class); // 过滤规则3(可选):只扫描带@Mapper的接口
// 3. 执行扫描,返回封装了BeanDefinition的Holder集合
Set<BeanDefinitionHolder> beanDefinitionHolders = scanner.scan(this.basePackage);
// 4. 后续注册逻辑(Spring自动完成)
processScanResult(beanDefinitionHolders, registry);
}
在scanner.scan(this.basePackage);内部就调用 processBeanDefinitions 完成 Spring 原生的 doScan 方法返回的 BeanDefinitionHolder 集合的加工
3.初始化扫描器,配置过滤规则(保证只扫描 Mapper 接口),还有个过滤规则3(可选)
// ClassPathMapperScanner 构造方法
public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
super(registry, false); // 禁用Spring原生默认过滤器(避免扫描@Component等注解类)
initFilters(); // 初始化自定义过滤规则
}
// 核心:初始化过滤规则
private void initFilters() {
// 过滤规则1:只保留「接口」(核心)
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return metadataReader.getClassMetadata().isInterface(); // 仅接口返回true
}
});
// 过滤规则2:排除Object.class等基础类
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.equals(Object.class.getName());
}
});
}
过滤规则是创建 BeanDefinition 的「前提」—— 只有符合规则的 UserMapper 接口才会进入后续创建流程;
3.执行扫描,scanner.scan(...) 会调用 Spring 原生的 doScan 方法
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> holders = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 步骤1:扫描出候选 BeanDefinition(candidate)—— 模板
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 步骤2:创建全新的 GenericBeanDefinition(bd)—— 正式文件
GenericBeanDefinition bd = new GenericBeanDefinition();
// 核心:从 candidate 拷贝核心数据到 bd(关联的关键)
bd.setBeanClassName(candidate.getBeanClassName()); // 拷贝类名(UserMapper)
bd.setBeanClass(candidate.getBeanClass()); // 拷贝类对象(UserMapper.class)
bd.setScope(candidate.getScope()); // 拷贝作用域(默认singleton)
bd.setLazyInit(candidate.isLazyInit()); // 拷贝懒加载配置
// 其他关键属性(注解、构造参数等)也会按需拷贝
// 步骤3:补充 MyBatis 扫描的默认配置(覆盖/补充)
bd.setScope(ScopeMetadata.SCOPE_SINGLETON); // 确保单例
// 步骤4:生成名称、包装为 Holder
String beanName = beanNameGenerator.generateBeanName(bd, registry);
BeanDefinitionHolder holder = new BeanDefinitionHolder(bd, beanName);
holders.add(holder);
}
}
return holders;
}
-
String beanName = beanNameGenerator.generateBeanName(bd, registry);为啥要传入registry?
简单说:registry 是名称生成器的 “环境感知器”—— 没有它,生成器就是 “闭门造车”,生成的名称可能不符合容器的实际环境(比如名字重复等等),导致注册失败或规则失效。 -
findCandidateComponents(basePackage);扫描器先解析 com.ylf.mapper 包的物理路径(比如 target/classes/com/example/mapper),然后遍历该路径下所有 .class 文件,得到待扫描列表:待扫描列表(示例): 1. UserMapper.class(接口,带@Mapper) 2. OrderMapper.class(接口,无@Mapper) 3. User.class(普通类) 4. Object.class(基础类) 5. TestUtil.class(普通工具类)扫描器对每个 .class 文件执行 “黑名单→白名单” 的过滤判断,只有全部通过的文件才会进入下一步:

最终保留的文件:只有 UserMapper.class。
为保留的 Mapper 接口创建 BeanDefinition (candidate) 即临时模板未完成加工之前的所有 BeanDefinition 的 beanclass都是接口本身
即:
findCandidateComponents(basePackage)返回的 candidate,实际类型是 ScannedGenericBeanDefinition(Spring 专为 “扫描.class 文件” 设计的子类),而非通用的 GenericBeanDefinition , 两种都是 BeanDefinition 接口的实现类。
4.给bean加工,processBeanDefinitions 是 MyBatis 对 BeanDefinition 的「核心改造」—— 将普通 BeanDefinition 改为「代理工厂类型」,使其能生成 Mapper 代理对象:
// org.mybatis.spring.mapper.ClassPathMapperScanner
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitionHolders) {
GenericBeanDefinition definition;
// 1. 遍历所有扫描得到的 Holder(每个对应一个 Mapper 接口)
for (BeanDefinitionHolder holder : beanDefinitionHolders) {
// 1.1 强制转换为 GenericBeanDefinition(保证可修改性)
// 此处 holder 中的 bd 是扫描阶段 new 的 GenericBeanDefinition(原生:类型=UserMapper)
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 1.2 获取 Mapper 接口全类名(如 com.example.mapper.UserMapper)
String mapperInterface = definition.getBeanClassName();
// ========== 核心加工1:修改 BeanDefinition 类型为 MapperFactoryBean ==========
// 把 bd 的类型从「UserMapper 接口」改为「MapperFactoryBean(代理工厂)」
// this.mapperFactoryBeanClass 默认值是 MapperFactoryBean.class
definition.setBeanClass(this.mapperFactoryBeanClass);
// ========== 核心加工2:添加构造参数(告诉代理工厂要代理哪个接口) ==========
// MapperFactoryBean 的构造方法:public MapperFactoryBean(Class<T> mapperInterface)
// 此处传入 UserMapper 接口的全类名,作为构造参数
definition.getConstructorArgumentValues().addGenericArgumentValue(mapperInterface);
// ========== 核心加工3:关联 SqlSessionFactory(代理工厂依赖) ==========
// 3.1 优先设置 SqlSessionFactory(通过 beanName 关联 Spring 容器中的 SqlSessionFactory Bean)
if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
} else if (this.sqlSessionFactoryBeanName != null) {
// 项目中常用方式:通过 beanName 关联(如 xml 中配置的 sqlSessionFactory)
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
}
// ========== 可选加工:关联 SqlSessionTemplate(按需) ==========
if (this.sqlSessionTemplate != null) {
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
} else if (this.sqlSessionTemplateBeanName != null) {
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
}
// ========== 可选加工:设置懒加载、自动扫描等 ==========
// 默认单例、非懒加载,可通过配置修改
if (this.lazyInitialization != null) {
definition.setLazyInit(this.lazyInitialization);
}
}
// 加工完成:holder 中的 bd 已被修改(引用传递,无需返回,直接生效)
}
5.注册阶段:MapperScannerConfigurer 源码(最终落地)
// ========== 核心注册逻辑 ==========
// 2. 遍历加工后的 holder,逐个注册到 Spring 注册表
for (BeanDefinitionHolder holder : beanDefinitionHolders) {
String beanName = holder.getBeanName(); // 如 userMapper
BeanDefinition bd = holder.getBeanDefinition(); // 加工后的 bd(类型=MapperFactoryBean)
// 2.1 注册 BeanDefinition 到 Spring 注册表(核心操作)
// registry 实际是 DefaultListableBeanFactory,底层用 Map 存储:beanName → bd
registry.registerBeanDefinition(beanName, bd);
// 2.2 注册别名(如果有)
String[] aliases = holder.getAliases();
if (aliases != null && aliases.length > 0) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
6.最终版的 BeanDefinition 有啥属性呢
-
- 构造参数(ConstructorArgumentValues),核心值:com.example.mapper.UserMapper(Mapper 接口全类名)告诉代理工厂要代理的接口;
-
- beanClass = MapperFactoryBean.class:告诉 Spring 用代理工厂创建实例;
-
- sqlSessionFactory 关联配置:让代理工厂能获取 MyBatis 核心组件。
7.那么spring为啥能自动 MapperScannerConfigurer 呢?
核心前提:MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口。
Spring 定义了一系列「容器扩展接口」,其中 BeanDefinitionRegistryPostProcessor 是专门用于在容器初始化早期修改 / 新增 BeanDefinition 的接口,其核心约定是:
所有实现该接口的 Bean,Spring 容器启动时会自动调用其 postProcessBeanDefinitionRegistry 方法,且执行时机远早于普通 Bean 的初始化(甚至早于 @Autowired 注入)。
执行步骤:
- 容器先解析所有配置生成 BeanDefinition;
- 筛选出 BeanDefinitionRegistryPostProcessor 类型的 BeanDefinition;
- 优先实例化并执行这些 Bean,其执行逻辑(扫描 Mapper)又会生成新的 BeanDefinition;
- 最后才处理普通 Bean —— 从效果上看,就像 “先扫描并执行了这个特殊 Bean”。

浙公网安备 33010602011771号