红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

Spring 源码学习 3:工厂后处理器

2025年6月21日 6点热度 0人点赞 0条评论

用途

ConfigurationClassPostProcessor

前文提到过,bean 工厂的后处理器可以用于处理 bean 定义。比如下面的示例:

static class Bean1{}
@Configuration
@ComponentScan(basePackages = "cn.icexmoon.demo.bean")
static class Config{
    @Bean
    public Bean1 bean1(){
        return new Bean1();
    }
}

bean1 由配置类的 bean 方法添加。bean2 位于包cn.icexmoon.demo.bean,由@componentScan开启的包扫描添加:

@Component
public class Bean2 {
}

创建容器并添加配置类:

GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBean(Config.class);
ctx.refresh();
printBeanDefinitionNames(ctx);
ctx.close();

执行后会发现容器中只有配置类的 bean 定义,缺少 bean1 和 bean2 的定义。

这个问题可以用工厂后处理器解决:

GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBean(Config.class);
ctx.registerBean(ConfigurationClassPostProcessor.class);
ctx.refresh();
printBeanDefinitionNames(ctx);
ctx.close();

工厂后处理器ConfigurationClassPostProcessor除了可以处理@Configuration、@ComponentScan、@Bean注解以外,还可以处理@Import和@ImportResource注解:

@Configuration
@ComponentScan(basePackages = "cn.icexmoon.demo.bean")
@Import(Bean3.class)
@ImportResource("classpath:bean4.xml")
static class Config{
    @Bean
    public Bean1 bean1(){
        return new Bean1();
    }
}
  • @Import注解用于将配置类或普通类添加为 Bean 定义。

  • @ImportResource 用于从 XML 配置中加载 Bean 定义。

MapperScannerConfigurer

使用工厂后处理器MapperScannerConfigurer可以自动扫描并添加 Mapper 到 bean 定义。

示例中有三个 Mapper (最后一个被有意错误定义为 class)位于包cn.icexmoon.demo.mapper中。

Mapper 依赖于数据库连接池等,因此需要在配置类中添加相应的 Bean:

@Configuration
static class DbConfig{
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
​
    @Bean
    public DruidDataSource druidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("mysql");
        return druidDataSource;
    }
}

需要添加相关依赖:

  • mybatis-spring-boot-starter

  • spring-boot-starter-data-jdbc

  • mysql-connector-j

  • druid

使用工厂后处理器扫描并添加 Mapper:

GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBean(DbConfig.class);
ctx.registerBean(ConfigurationClassPostProcessor.class);
ctx.registerBean(MapperScannerConfigurer.class, db->{
db.getPropertyValues().add("basePackage", "cn.icexmoon.demo.mapper");
});
ctx.refresh();
printBeanDefinitionNames(ctx);
ctx.close();

为了指定在哪个包下扫描 Mapper,这里通过registerBean的参数传入一个匿名函数,其参数类型为BeanDefinition,可以利用它在共产后处理器创建时修改其属性,比如这里就是修改MapperScannerConfigurer 的 bean 属性basePackage为指定的包。

原理

@ComponentScan

下面通过自定义一个处理@ComponentScan注解的自定义工厂后处理器,来演示工厂后处理器的工作原理。

配置类:

@Configuration
@ComponentScan(basePackages = "cn.icexmoon.demo.bean2")
static class Config {
}

包cn.icexmoon.demo.bean2中包含三个类:

@Component
public class Bean1 {
}
@Service
public class Bean2 {
}
public class Bean3 {
}

自定义工厂后处理器:

@SneakyThrows
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    // 查找具有 @Configuration 注解的 配置类
    String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
        String beanClassName = beanDefinition.getBeanClassName();
        Class<?> beanClass = Class.forName(beanClassName);
        // 判断类型是否有 @component 注解 和 @componentScan 注解
        Configuration configuration = AnnotationUtils.findAnnotation(beanClass, Configuration.class);
        ComponentScan componentScan = AnnotationUtils.findAnnotation(beanClass, ComponentScan.class);
        if (configuration != null && componentScan != null) {
            // 同时具有 @Component 注解和 @ComponentScan 注解
            log.info("找到匹配的类" + beanClassName);
            // 获取要处理的包
            String[] basePackages = componentScan.basePackages();
            for (String basePackage : basePackages) {
                // 获取包对应的类路径,比如 classpath*:cn/icexmoon/demo/bean2/**/*.class
                String classpath = "classpath*:%s/**/*.class".formatted(basePackage.replace('.', '/'));
                log.info("classpath:" + classpath);
                // 加载字节码文件(代码运行时没有源码,只有字节码)
                Resource[] resources = new PathMatchingResourcePatternResolver().getResources(classpath);
                CachingMetadataReaderFactory cachingMetadataReaderFactory = new CachingMetadataReaderFactory();
                for (Resource resource : resources) {
                    // 要处理的类对应的字节码
                    log.info("class file:" + resource.getFilename());
                    // 读取类的元信息
                    MetadataReader metadataReader = cachingMetadataReaderFactory.getMetadataReader(resource);
                    // 判断类是否有 @Component 注解或其子注解
                    AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
                    if (annotationMetadata.hasAnnotation(Component.class.getName())
                        || annotationMetadata.hasMetaAnnotation(Component.class.getName())) {
                        // 获取要处理的字节码的类名
                        String className = metadataReader.getClassMetadata().getClassName();
                        // 添加类定义
                        // 方便起见,这里默认容器类型为 GenericApplicationContext
                        if (beanFactory instanceof DefaultListableBeanFactory defaultListableBeanFactory) {
                            log.info("类[%s]被添加到容器中".formatted(className));
                            AbstractBeanDefinition bb = BeanDefinitionBuilder.genericBeanDefinition(className)
                                .setScope("singleton")
                                .getBeanDefinition();
                            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                            defaultListableBeanFactory.registerBeanDefinition(generator.generateBeanName(bb, defaultListableBeanFactory), bb);
                        }
                    }
                }
            }
        }
    }
}

代码比较多,但逻辑并不复杂:

  1. 获取要处理的配置类以及 @ComponentScan 注解

  2. 获取要处理的包名

  3. 根据包名获取到对应的字节码目录

  4. 获取对应的字节码文件

  5. 如果字节码文件有 @Component 相关注解,给工厂添加 bean 定义

这里借助两个工具类获取注解信息:

  • AnnotationUtils,获取类中的注解信息

  • CachingMetadataReaderFactory,获取资源文件(这里是字节码)中的注解信息

注意,这里使用的 AnnotationUtils 是 Spring 框架的,Junit 有一个同名类。

通过容器加载配置类和工厂后处理器:

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(Config.class);
context.registerBean(ComponentScanPostProcessor.class);
context.refresh();
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println(beanDefinitionName);
}
context.close();

@Bean

下面演示怎么用自定义工厂后处理器处理配置类中的 bean 方法。

示例类型:

@Configuration
public class SingleConfig {
    @Bean
    public Bean1 bean1(){
        return new Bean1();
    }
​
    @Bean
    public Bean2 bean2(Bean1 bean1){
        return new Bean2(bean1);
    }
​
    @Bean(initMethod = "init")
    public Bean3 bean3(){
        return new Bean3();
    }
}
public class Bean1 {
}
public class Bean2 {
    private Bean1 bean1;
​
    public Bean2(Bean1 bean1) {
        this.bean1 = bean1;
    }
}
public class Bean3 {
    public void init(){
        System.out.println("Bean3 init");
    }
}

简单起见,这里没有再使用内部类作为配置类,而是单独的类。此外,配置类用 bean 方法定义了三个 bean,分别是普通的 bean,通过参数注入属性的 bean,以及指定方法作为初始化方法的 bean。

创建自定义工厂后处理器:

static class BeanMethodPostProcessor implements BeanFactoryPostProcessor {
    @SneakyThrows
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
            String beanClassName = beanDefinition.getBeanClassName();
            // 处理带 @Configuration 注解的 bean 定义
            Class<?> beanClass = Class.forName(beanClassName);
            Configuration configuration = AnnotationUtils.findAnnotation(beanClass, Configuration.class);
            if (configuration != null) {
                // 获取 bean 方法
                CachingMetadataReaderFactory cachingMetadataReaderFactory = new CachingMetadataReaderFactory();
                // 简单起见,这里默认所有配置类都是单独的配置类(非内部类)
                String classpath = beanClassName.replace('.', '/') + ".class";
                Resource resource = new PathMatchingResourcePatternResolver().getResource("classpath:" + classpath);
                MetadataReader metadataReader = cachingMetadataReaderFactory.getMetadataReader(resource);
                Set<MethodMetadata> annotatedMethods = metadataReader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
                for (MethodMetadata annotatedMethod : annotatedMethods) {
                    log.info(annotatedMethod.getMethodName());
                    // 构造 bean 定义
                    // 将 bean 方法用于 bean 创建的工厂方法
                    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition()
                        // 处理工厂方法参数注入
                        .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR)
                        .setFactoryMethodOnBean(annotatedMethod.getMethodName(), beanDefinitionName);
                    // 如果 bean 注解设置了 init 方法
                    String initMethod = (String) annotatedMethod.getAnnotationAttributes(Bean.class.getName()).get("initMethod");
                    if (initMethod != null && !initMethod.isEmpty()) {
                        beanDefinitionBuilder.setInitMethodName(initMethod);
                    }
                    AbstractBeanDefinition bd = beanDefinitionBuilder
                        .getBeanDefinition();
                    if (beanFactory instanceof DefaultListableBeanFactory defaultListableBeanFactory) {
                        defaultListableBeanFactory.registerBeanDefinition(annotatedMethod.getMethodName(), bd);
                    }
                }
            }
        }
    }
}

将配置类和工厂后处理器添加到容器:

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(SingleConfig.class);
context.registerBean(BeanMethodPostProcessor.class);
context.refresh();
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println(beanDefinitionName);
}
context.close();

Mapper

如果要用”手动“的方式添加 Mapper 的 bean 定义:

@Configuration
static class DbConfig{
    // ...
​
    @Bean
    public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory){
        MapperFactoryBean<Mapper1> mapperFactoryBean = new MapperFactoryBean<>(Mapper1.class);
        mapperFactoryBean.setSqlSessionFactory(sqlSessionFactory);
        return mapperFactoryBean;
    }
​
    @Bean
    public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory){
        MapperFactoryBean<Mapper2> mapperFactoryBean = new MapperFactoryBean<>(Mapper2.class);
        mapperFactoryBean.setSqlSessionFactory(sqlSessionFactory);
        return mapperFactoryBean;
    }
}

在项目中依次这样手动创建并不现实。因此可以用工厂的后处理器来扫描和创建。

static class MapperScanPostProcessor implements BeanDefinitionRegistryPostProcessor {
    // Mapper 所在包
    @Setter
    private String basePackage;
​
    @Override
    @SneakyThrows
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 在 bean 工厂加载了所有 bean 定义后调用
        // 扫描并加载 Mapper
        if (basePackage == null || basePackage.isEmpty()){
            return;
        }
        // 从包路径扫描并加载字节码
        String locationPatter = "classpath*:%s/**/*.class".formatted(basePackage.replace('.', '/'));
        Resource[] resources = new PathMatchingResourcePatternResolver().getResources(locationPatter);
        CachingMetadataReaderFactory cachingMetadataReaderFactory = new CachingMetadataReaderFactory();
        for (Resource resource : resources) {
            // 获取元信息
            MetadataReader metadataReader = cachingMetadataReaderFactory.getMetadataReader(resource);
            // 只处理接口
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
            if(classMetadata.isInterface()){
                // 构造 bean 定义
                AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
                    .addConstructorArgValue(classMetadata.getClassName())
                    .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                    .getBeanDefinition();
                // 生成名称
                AbstractBeanDefinition beanNameDefinition = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName())
                    .getBeanDefinition();
                AnnotationBeanNameGenerator annotationBeanNameGenerator = new AnnotationBeanNameGenerator();
                String beanName = annotationBeanNameGenerator.generateBeanName(beanNameDefinition, registry);
                registry.registerBeanDefinition(beanName, beanDefinition);
            }
        }
    }
​
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinitionRegistryPostProcessor.super.postProcessBeanFactory(beanFactory);
    }
}

使用后处理器扫描 Mapper:

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(DbConfig2.class);
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(MapperScanPostProcessor.class, bd -> {
    bd.getPropertyValues().add("basePackage", "cn.icexmoon.demo.mapper");
});
context.refresh();
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println(beanDefinitionName);
}
context.close();

本文的完整示例可以从这里获取。

The End.

参考资料

  • 黑马程序员Spring视频教程,深度讲解spring5底层原理

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: spring
最后更新:2025年6月21日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号