# spring-config-example **Repository Path**: jonathanzyf/spring-config-example ## Basic Information - **Project Name**: spring-config-example - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-12-17 - **Last Updated**: 2021-11-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Spring Boot 配置示例工程 ## 简介 本工程给出了Spring配置相关的是测试代码,目前包括如下模块: |模块|测试内容|重要的类| |---|---|---| |beanDefinition|通过@Import机制,批量创建BeanDefinition|ImportBeanDefinitionRegistrar, ClassPathScanningCandidateComponentProvider, BeanDefinitionBuilder, BeanDefinition, AnnotationMetadata| |sequence|加载过程中扩展机制加载顺序|EnvironmentPostProcessor, ImportSelector, DeferredImportSelector, ImportBeanDefinitionRegistrar, BeanDefinitionRegistryPostProcessor, BeanFactoryPostProcessor, *Aware| |componentscan|Spring Boot中@SpringBootApplication与@ComponentScan同时使用影响分析|ConfigurationClassPostProcessor, ConfigurationClassParser, AnnotationConfigUtils, AnnotatedTypeMetadata, ComponentScanAnnotationParser, ClassPathBeanDefinitionScanner| |conditional|Spring Boot中@Conditional使用|@Conditional, @ConditionalOnMissingBean, @ConditionalOnProperty, @Profile, ConfigurationClassUtils| ConfigDataEnvironmentPostProcessor implements EnvironmentPostProcessor
ConfigFileApplicationListener implements EnvironmentPostProcessor
ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor ## Spring主要的扩展机制 |重要接口|说明| |---|---| |ImportSelector|BeanDefinition阶段,立即选择要引入的类 e.g. Configuration| |DeferredImportSelector|BeanDefinition阶段,延迟选择要引入的类,通常结合条件@Conditional。| |ImportBeanDefinitionRegistrar|BeanDefinition阶段,额外增加Bean定义到BeanDefinitionRegistry。| |EnvironmentAware|BeanDefinition阶段,注入Spring环境变量| |ResourceLoaderAware|BeanDefinition阶段,注入Spring资源加载器| |BeanFactoryAware|BeanDefinition阶段,注入BeanFactory| |BeanClassLoaderAware|BeanDefinition阶段,注入Spring类加载器| |BeanFactoryPostProcessor|BeanDefinition阶段,在所有Bean初始化之前,增加或修改BeanDefinition| |BeanDefinitionRegistryPostProcessor|BeanDefinition阶段,在所有Bean初始化之前,增加或修改BeanDefinition,继承自BeanFactoryPostProcessor| |BeanPostProcessor|Bean阶段,在Bean初始化并设置属性后执行其before方法,在afterPropertiesSet()方法后执行其after方法。| |ApplicationContextAware|Bean阶段,注入ApplicationContext| |InitializingBean|Bean阶段,在Bean初始化并设置属性后执行其afterPropertiesSet()方法。| |DisposableBean|Bean阶段,在Bean销毁前执行其destroy方法,用以释放资源| |SmartLifecycle|Bean阶段,spring容器通知调用其start/stop方法,并可以设置自动开始。常用于资源管理器。| ## Loading Sequence 示例 扩展机制执行顺序 1. EnvironmentPostProcessor postProcessEnvironment() 2. ImportSelector selectImports() 3. DeferredImportSelector selectImports() 4. ImportBeanDefinitionRegistrar registerBeanDefinitions() 5. BeanDefinitionRegistryPostProcessor postProcessBeanDefinitionRegistry()+postProcessBeanFactory() 6. BeanFactoryPostProcessor postProcessBeanFactory() 7. BeanFactoryAware 8. BeanClassLoaderAware 9. EnvironmentAware 10. ResourceLoaderAware 11. ApplicationContextAware Bean加载执行顺序 1. @Component 2. BeanPostProcessor postProcessBeforeInitialization 3. InitializingBean 4. BeanPostProcessor postProcessAfterInitialization 5. ImportSelector -> BeanDefinition 6. @Bean 7. ImportBeanDefinitionRegistrar -> BeanDefinition 8. DeferredImportSelector -> BeanDefinition 9. BeanDefinitionRegistryPostProcessor -> BeanDefinition 10. BeanFactoryPostProcessor -> BeanDefinition 11. SmartLifecycle 12. ApplicationListener 1、5、6、7在8、9、10之前执行,均为创建BeanDefinition实例;其中2、3、4顺序固定,为访问Bean属性,在Bean创建之后执行(对于每个Bean,1、5、6、7、8、9、10均在2、3、4之前执行);11最后执行,一般为容器/资源/管理器类型Bean。 `ApplicationListener`监听应用启动完成事件,这时Bean都已经准备好了,这时可以加载资源,例如缓存预热。 本案例执行日志 ```markdown 1. [MyEnvironmentPostProcessor] postProcessEnvironment 2. [MyImportSelector] selectImports 3. [MyDeferredImportSelector] selectImports 4. [MyImportBeanDefinitionRegistrar] registerBeanDefinitions 5. [MyBeanDefinitionRegistryPostProcessor] postProcessBeanDefinitionRegistry 6. [MyBeanDefinitionRegistryPostProcessor] postProcessBeanFactory 7. [MyBeanFactoryPostProcessor] postProcessBeanFactory 8. [SpringConfigAutoConfig] setBeanFactory 9. [SpringConfigAutoConfig] setBeanClassLoader 10. [SpringConfigAutoConfig] setBeanFactory 11. [SpringConfigAutoConfig] setEnvironment 12. [SpringConfigAutoConfig] setResourceLoader 13. [SpringConfigAutoConfig] setApplicationContext 14. [MyBeanPostProcessorBean] constructor creating 15. [MyBeanPostProcessor] postProcessBeforeInitialization 16. [MyBeanPostProcessorBean] myBeanPostProcessorBean setProperty1 from {prop1 in postProcessEnvironment} to {prop1 in Before postProcessBeforeInitialization} 17. [MyBeanPostProcessorBean] myBeanPostProcessorBean afterPropertiesSet 18. [MyBeanPostProcessorBean] myBeanPostProcessorBean prop1 is {prop1 in Before postProcessBeforeInitialization} 19. [MyBeanPostProcessor] postProcessAfterInitialization 20. [MyBeanPostProcessorBean] myBeanPostProcessorBean setProperty1 from {prop1 in Before postProcessBeforeInitialization} to {prop1 in After postProcessAfterInitialization} 21. [MyImportSelectorBean] constructor creating 22. [SpringConfigAutoConfig] creating myConfigurationBean 23. [MyConfigurationBean] constructor creating 24. [MyBeanPostProcessorBean] constructor creating 25. [MyBeanPostProcessor] postProcessBeforeInitialization 26. [MyBeanPostProcessorBean] myBeanPostProcessorBean2 setProperty1 from {prop1 in postProcessEnvironment} to {prop1 in Before postProcessBeforeInitialization} 27. [MyBeanPostProcessorBean] myBeanPostProcessorBean2 afterPropertiesSet 28. [MyBeanPostProcessorBean] myBeanPostProcessorBean2 prop1 is {prop1 in Before postProcessBeforeInitialization} 29. [MyBeanPostProcessor] postProcessAfterInitialization 30. [MyBeanPostProcessorBean] myBeanPostProcessorBean2 setProperty1 from {prop1 in Before postProcessBeforeInitialization} to {prop1 in After postProcessAfterInitialization} 31. [MyImportBeanDefinitionRegistrarBean] constructor creating 32. [SpringConfigApplication] creating MyApplicationListener 33. [MyDeferredImportSelectorBean] constructor creating 34. [MyBeanDefinitionRegistryPostProcessorBean] constructor creating 35. [MyBeanFactoryPostProcessorBean] constructor creating 36. [MyBeanPostProcessorBean] myBeanPostProcessorBean isRunning 37. [MyBeanPostProcessorBean] myBeanPostProcessorBean start 38. [MyBeanPostProcessorBean] myBeanPostProcessorBean2 isRunning 39. [MyBeanPostProcessorBean] myBeanPostProcessorBean2 start 40. [MyApplicationListener] onApplicationEvent ``` ## BeanDefinition 示例 通过@Import机制,模拟批量创建设置了@FeignClient注解的BeanDefinition,最终创建FeignClientProxy实例单例对象。 ```java public class EnableFeignAutoConfigRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); // scanner.setResourceLoader(this.resourceLoader); scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); String defaultPackage = ClassUtils.getPackageName(importingClassMetadata.getClassName()); Set beanDefinitions = scanner.findCandidateComponents(defaultPackage); for (BeanDefinition bd : beanDefinitions) { AnnotatedBeanDefinition abd = (AnnotatedBeanDefinition) bd; AnnotationMetadata am = abd.getMetadata(); Map attributes = am.getAnnotationAttributes(FeignClient.class.getName()); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientProxy.class) .addPropertyValue("url", attributes.get("url")) .addPropertyValue("name", attributes.get("name")); registry.registerBeanDefinition(ClassUtils.getShortName(am.getClassName()), builder.getBeanDefinition()); } } } ``` ```java @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Inherited @Documented @Import({EnableFeignAutoConfigRegistrar.class}) public @interface EnableFeignClient { } ``` ## conditional 示例 ```java @Configuration // 扫描 @SpringBootApplication 所在包之外的路径 @ComponentScan(basePackages = "org.example.config.conditional.apm.beans") // @Import优先级高于@Bean @Import(ApmAutoConfig.class) public class MockAutoConfig { // 下面 @Profile 和 @ConditionalOnProperty 两个实现功能相同,都是仅在测试环境下创建mockUser1 Bean。 @Profile("test") @ConditionalOnProperty(prefix = "spring.profiles", name = "active", havingValue = "test") @Bean // 当没有其他类型为MockUser的BeanDefinition时,才创建此mockUser1 Bean。 @ConditionalOnMissingBean(MockUser.class) public MockUser mockUser1() { return new MockUser(); } } ``` ## ComponentScan 示例 ### 仅仅使用 @SpringBootApplication ```java @SpringBootApplication public class ComponentScanApplication { } ``` 结果: ```shell mockConfig1 mockConfig2 mockInstance1 mockInstance2 ``` ### 使用 @SpringBootApplication 的 scanBasePackages ```java @SpringBootApplication( scanBasePackages = {"org.example.config.componentscan"} ) public class ComponentScanApplication { } ``` 值为`org.example.config.componentscan`时,结果: ```shell mockConfig1 mockConfig2 mockInstance1 mockInstance2 ``` 值为`org.example.config.componentscan.package1`时,结果: ```shell mockConfig1 mockInstance1 ``` ### 与 @SpringBootApplication 同时使用 @ComponentScan,声明在一个类上 ```java @SpringBootApplication @ComponentScan({"org.example.config.componentscan"}) public class ComponentScanApplication { } ``` 值为`org.example.config.componentscan`时,结果: ```shell mockConfig1 mockConfig2 mockInstance1 mockInstance2 ``` 值为`org.example.config.componentscan.package1`时,结果: ```shell mockConfig1 mockInstance1 ``` ### 与 @SpringBootApplication 同时使用 @ComponentScan,但不声明在一个类上,类位于下一层级 ```java @SpringBootApplication public class ComponentScanApplication { } ``` ```java @Configuration @ComponentScan({"org.example.config.other"}) public class ScanAutoConfig { } ``` 结果: ```shell mockConfig1 mockConfig2 mockOtherAutoConfig mockOtherInstance mockInstance1 mockInstance2 ``` ### 与 @SpringBootApplication 同时使用 @ComponentScan,但不声明在一个类上,类位于同一层级 效果同上 `与 @SpringBootApplication 同时使用 @ComponentScan,但不声明在一个类上,类位于下一层级` ### 与 @SpringBootApplication 同时使用多个 @ComponentScan,声明在一个类上 ```java @SpringBootApplication @ComponentScan("org.example.config.componentscan.package1") @ComponentScan("org.example.config.componentscan.package1") @ComponentScan("org.example.config.componentscan.package2") @ComponentScan("org.example.config.componentscan.package2") public class ComponentScanApplication { } ``` ```java @Configuration @ComponentScan({"org.example.config.other"}) public class ScanAutoConfig { } ``` 结果: ```shell mockConfig1 mockConfig2 mockOtherAutoConfig mockOtherInstance mockInstance1 mockInstance2 ``` ### 代码跟踪 ```shell SpringApplication.run() prepareContext(context, environment, listeners, applicationArguments, printedBanner) org.springframework.boot.BeanDefinitionLoader.load() # 将自身BeanDefinition加入Registry org.springframework.context.annotation.AnnotatedBeanDefinitionReader.register(source) org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(String beanName, BeanDefinition beanDefinition) refreshContext(context) org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry registry) org.springframework.context.annotation.ConfigurationClassParser.parse(Set configCandidates) # 依次处理 PropertySource, ComponentScan, ImportResource # ComponentScan时,参数为 sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class org.springframework.context.annotation.AnnotationConfigUtils.attributesForRepeatable(AnnotationMetadata metadata, Class containerClass, Class annotationClass) # 使用了 MergedAnnotationSelectors.firstDirectlyDeclared(),只能返回一个最直接的@ComponentScan # 这也是为什么自身声明了@ComponentScan后,@SpringBootApplication中定义了basePackages或basePackageClasses也无法生效的原因 org.springframework.core.type.AnnotatedTypeMetadata.getAnnotationAttributes(String annotationName, boolean classValuesAsString) # FirstDirectlyDeclared, Nearest MergedAnnotation annotation = getAnnotations().get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared()); org.springframework.context.annotation.ComponentScanAnnotationParser.parse(AnnotationAttributes componentScan, final String declaringClass) # 如果自身所有@ComponentScan没有指定basePackages或basePackageClasses,将自身package加入 # 这也是为什么@SpringBootApplication未指定basePackages会扫描自身package中所有的类,如果在自身使用@ComponentScan,指定了basePackages或basePackageClasses,则此判断则跳过; # 如果不是在自身上声明的@ComponentScan,则不受影响 if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(declaringClass)); } ... org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(String... basePackages) ``` ### 结论 1. @SpringBootApplication 声明在启动类上,不设置basePackages或basePackageClasses,将使用启动类的package作为ClassPathBeanDefinitionScanner的basePackages参数。 2. @SpringBootApplication 与 @ComponentScan 同时声明在同一个启动类上,@SpringBootApplication内置的@ComponentScan将不生效。 3. @SpringBootApplication 与 @ComponentScan 声明在不同的类上,两者都起作用 4. @SpringBootApplication 与 多个@ComponentScan 同时声明在同一个启动类上,@SpringBootApplication和多个@ComponentScan都生效。原因是先查找了@ComponentScan,又查找了@ComponentScans