6.Spring5底层原理之BeanFactory后处理器,模拟@ComponentScan

常见的BeanFactory后处理器

为了演示常见的BeanFactory后处理器,我们来创建一些类。

@Configuration
@ComponentScan("com.zhaojun.springsource.a05.component")
public class Config {

    @Bean
    public Bean1 bean1(){
        return new Bean1();
    }

    @Bean
    public SqlSessionFactoryBean  sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean(initMethod = "init")
    public DruidDataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("123");
        return dataSource;
    }
}

首先是Config类,该类用@Configuration注解声明是一个配置类,并且用@ComponentScan指定了包扫描的路径。然后在该类中注册了三个bean,分别是bean1、sqlSessionFactoryBean、dataSource。

这里需要引入Druid包和mysql数据库驱动相关的依赖。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.22</version>
</dependency>

然后是Bean1

public class Bean1 {
}

component包下有三个类分别是:

@Component()
public class Bean2 {
    private static final Logger log = LoggerFactory.getLogger(Bean2.class);

    public Bean2(){
        log.debug("被Spring管理,bean2");
    }
}
@Controller
public class Bean3 {
    private static final Logger log = LoggerFactory.getLogger(Bean3.class);

    public Bean3(){
        log.debug("被Spring管理,bean3");
    }
}
public class Bean4 {
    private static final Logger log = LoggerFactory.getLogger(Bean4.class);

    public Bean4(){
        log.debug("被Spring管理,bean4");
    }
}

我们写个main方法测试一下

public class A05Application {
    public static final Logger log = LoggerFactory.getLogger(A05Application.class);

    public static void main(String[] args) throws IOException {
        GenericApplicationContext context = new GenericApplicationContext();

        context.registerBean("config", Config.class);

        context.refresh();

        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }

        context.close();
    }
}

GenericApplicationContext在上一节中我们已经讲过了,没有包含后处理器的,运行main方法,输出日志如下

15:35:24.224 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@42607a4f
15:35:24.262 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
config
15:35:24.322 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@42607a4f, started on Fri May 06 15:35:24 CST 2022

可以看到,只有Config类,被加载到BeanDefinitionNames中,其他的bean都没有被加载,说明@Bean没有生效。我们还记得之前讲的,解析@Bean会用到ConfigurationClassPostProcessor后处理器。我们把后处理器加上再试试。

public static void main(String[] args) throws IOException {
    GenericApplicationContext context = new GenericApplicationContext();

    context.registerBean("config", Config.class);
	context.registerBean(ConfigurationClassPostProcessor.class); //@ComponentScan // @Configuration @Bean @Import @ImportResource
    
    context.refresh();

    for (String beanDefinitionName : context.getBeanDefinitionNames()) {
        System.out.println(beanDefinitionName);
    }

    context.close();
}

输出日志

15:47:19.522 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@42607a4f
15:47:19.551 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.ConfigurationClassPostProcessor'
15:47:19.696 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [D:\IDEAWorkSpace\spring-source\target\classes\com\zhaojun\springsource\a05\component\Bean2.class]
15:47:19.698 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [D:\IDEAWorkSpace\spring-source\target\classes\com\zhaojun\springsource\a05\component\Bean3.class]
15:47:19.825 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
15:47:19.826 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
15:47:19.826 [main] DEBUG com.zhaojun.springsource.a05.component.Bean2 - 被Spring管理,bean2
15:47:19.826 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean3'
15:47:19.826 [main] DEBUG com.zhaojun.springsource.a05.component.Bean3 - 被Spring管理,bean3
15:47:19.826 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean1'
15:47:19.839 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSessionFactoryBean'
15:47:19.841 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dataSource'
15:47:20.053 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
15:47:20.053 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'sqlSessionFactoryBean' via factory method to bean named 'dataSource'
15:47:20.064 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
15:47:20.071 [main] DEBUG org.mybatis.spring.SqlSessionFactoryBean - Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration
15:47:20.151 [main] DEBUG org.mybatis.spring.SqlSessionFactoryBean - Property 'mapperLocations' was not specified.
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
bean2
bean3
bean1
sqlSessionFactoryBean
dataSource
15:47:20.170 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@42607a4f, started on Fri May 06 15:47:19 CST 2022
15:47:20.171 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} closing ...
15:47:20.172 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} closed

可以看到,加了后处理器之后,@Bean生效,@ComponentScan也生效了。

模拟解析@ComponentScan

我们自己模拟解析@ComponentScan注解,了解下@ComponentScan的原理。

public class A05Application {
    public static final Logger log = LoggerFactory.getLogger(A05Application.class);

    public static void main(String[] args) throws IOException {
        GenericApplicationContext context = new GenericApplicationContext();

        context.registerBean("config", Config.class);

         //自己实现 解析@Componment注解
        
        // 检验Config类,是否有ComponentScan注解
        ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
        BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
        DefaultListableBeanFactory defaultListableBeanFactory = context.getDefaultListableBeanFactory();
        if (componentScan != null) {
            // 获取ComponentScan配置的basePackages
            for (String basePackage : componentScan.basePackages()) {
                CachingMetadataReaderFactory cachingMetadataReaderFactory = new CachingMetadataReaderFactory();
                // 将包路径,转换为Spring资源路径格式
                String path = "classpath*:" + basePackage.replace(".", "/") + "/**/*.class";
                Resource[] resources = context.getResources(path);
                for (Resource resource : resources) {
                    // 获取类的元信息,包含类名,注解等信息
                    MetadataReader metadataReader = cachingMetadataReaderFactory.getMetadataReader(resource);
                    AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
				   // System.out.print(metadataReader.getClassMetadata().getClassName() + ":");
                    // 校验是否有@Component注解
                    // System.out.print(metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName()) + ",");
                    // 校验是否有Component的派生注解  比如@Controller注解
                    // System.out.print(metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName()));
                    if (annotationMetadata.hasAnnotation(Component.class.getName())
                            || annotationMetadata.hasMetaAnnotation(Component.class.getName())
                    ) {
                        // 构建BeanDefinition
                        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(metadataReader.getClassMetadata().getClassName()).getBeanDefinition();
                        // 创建beanName
                        String beanName = beanNameGenerator.generateBeanName(beanDefinition, defaultListableBeanFactory);
                        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinition);
                    }
                }
            }
        }

        context.refresh();

        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }

        context.close();
    }
}

输出日志

16:06:15.788 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@1990a65e
16:06:15.823 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
16:06:15.837 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
16:06:15.837 [main] DEBUG com.zhaojun.springsource.a05.component.Bean2 - 被Spring管理,bean2
16:06:15.837 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean3'
16:06:15.838 [main] DEBUG com.zhaojun.springsource.a05.component.Bean3 - 被Spring管理,bean3
config
bean2
bean3
16:06:15.862 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@1990a65e, started on Fri May 06 16:06:15 CST 2022

流程:

  1. 首先,我们需要判断Config类是否被@Componment标注。
  2. 如果有,然后获取要扫描的包路径,将配置的包路径,转换为Spring扫描资源的路径格式。
  3. 搜索路径下的所有类,判断哪些类被@Component或者@Component的派生注解标注。
  4. 为这些类生成beanDefinition,注册到BeanFactory中。

将模拟代码封装为自定义后处理器

我们将上面的代码封装为一个BeanFactory后处理器,需要实现BeanFactoryPostProcessor接口,将上面的代码放到postProcessBeanFactory()方法中,适当调整一下,postProcessBeanFactory()方法在context.refresh()会调用。

public class ComponentScanPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            // 自己实现 解析@Componment注解
            ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
            BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
            if (componentScan != null) {
                for (String basePackage : componentScan.basePackages()) {
                    CachingMetadataReaderFactory cachingMetadataReaderFactory = new CachingMetadataReaderFactory();
                    String path = "classpath*:" + basePackage.replace(".", "/") + "/**/*.class";
                    //由于没有context用 new PathMatchingResourcePatternResolver() 代替,功能一样
                    Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
                    for (Resource resource : resources) {
                        MetadataReader metadataReader = cachingMetadataReaderFactory.getMetadataReader(resource);
                        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

                        if (annotationMetadata.hasAnnotation(Component.class.getName())
                                || annotationMetadata.hasMetaAnnotation(Component.class.getName())
                        ) {
                            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(metadataReader.getClassMetadata().getClassName()).getBeanDefinition();
                            // 由于该方法中的参数类型是ConfigurableListableBeanFactory,为DefaultListableBeanFactory的父类
                            // 没有registerBeanDefinition方法,所以,需要判断是否是DefaultListableBeanFactory,然后转换类型
                            if (beanFactory instanceof DefaultListableBeanFactory){
                                DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
                                String beanName = beanNameGenerator.generateBeanName(beanDefinition, defaultListableBeanFactory);
                                defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinition);
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用

public static void main(String[] args) throws IOException {
    GenericApplicationContext context = new GenericApplicationContext();

    context.registerBean("config", Config.class);
    // 将这个bean进行注册即可使用了
    context.registerBean(ComponentScanPostProcessor.class);

    context.refresh();

    for (String beanDefinitionName : context.getBeanDefinitionNames()) {
        System.out.println(beanDefinitionName);
    }

    context.close();
}

来看看日志输出

16:51:57.113 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@42607a4f
16:51:57.159 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'com.zhaojun.springsource.a05.ComponentScanPostProcessor'
16:51:57.377 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
16:51:57.377 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
16:51:57.377 [main] DEBUG com.zhaojun.springsource.a05.component.Bean2 - 被Spring管理,bean2
16:51:57.378 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean3'
16:51:57.378 [main] DEBUG com.zhaojun.springsource.a05.component.Bean3 - 被Spring管理,bean3
config
com.zhaojun.springsource.a05.ComponentScanPostProcessor
bean2
bean3
16:51:57.420 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@42607a4f, started on Fri May 06 16:51:57 CST 2022

总结

这一节我们了解了BeanFactory后处理器ConfigurationClassPostProcessor的作用,还通过自己模拟@ComponentScan的执行流程,自己封装了一个beanFactory后处理器。


6.Spring5底层原理之BeanFactory后处理器,模拟@ComponentScan
https://www.zhaojun.inkhttps://www.zhaojun.ink/archives/spring-beanfactory-post-processor-componentscan
作者
卑微幻想家
发布于
2022-05-06
许可协议