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 文件执行 “黑名单→白名单” 的过滤判断,只有全部通过的文件才会进入下一步:

    image

    最终保留的文件:只有 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 有啥属性呢

    1. 构造参数(ConstructorArgumentValues),核心值:com.example.mapper.UserMapper(Mapper 接口全类名)告诉代理工厂要代理的接口;
    1. beanClass = MapperFactoryBean.class:告诉 Spring 用代理工厂创建实例;
    1. sqlSessionFactory 关联配置:让代理工厂能获取 MyBatis 核心组件。

7.那么spring为啥能自动 MapperScannerConfigurer 呢?

核心前提:MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口。

Spring 定义了一系列「容器扩展接口」,其中 BeanDefinitionRegistryPostProcessor 是专门用于在容器初始化早期修改 / 新增 BeanDefinition 的接口,其核心约定是:
所有实现该接口的 Bean,Spring 容器启动时会自动调用其 postProcessBeanDefinitionRegistry 方法,且执行时机远早于普通 Bean 的初始化(甚至早于 @Autowired 注入)。

执行步骤

  • 容器先解析所有配置生成 BeanDefinition;
  • 筛选出 BeanDefinitionRegistryPostProcessor 类型的 BeanDefinition;
  • 优先实例化并执行这些 Bean,其执行逻辑(扫描 Mapper)又会生成新的 BeanDefinition;
  • 最后才处理普通 Bean —— 从效果上看,就像 “先扫描并执行了这个特殊 Bean”。
posted @ 2025-12-07 10:27  那就改变世界吧  阅读(1)  评论(0)    收藏  举报