# 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