提問
- 服務啟動的時候,SpringApplication內部做了什么?
- 創建上下文的時候是使用的哪一種ApplicationContext?
- Bean 是在哪個步驟定義的,BeanDefinition怎么排序的?
- Bean 是在哪個步驟創建和初始化的?
- 監聽器有什么作用,都發布了哪些事件?
本篇文章主要介紹Bean是什么時候定義的,及Bean是如何創建的。
實例講解
application.yml
client:
id: 1
server: localhost
MyClientAutoConfiguration
@Configuration
@EnableConfigurationProperties(ClientProperties.class)
@ConditionalOnProperty(prefix = "client", name = "enable", havingValue = "true")
public class MyClientAutoConfiguration {
?
private ClientProperties properties;
@Autowired
public MyClientAutoConfiguration(ClientProperties properties) {
this.properties = properties;
}
?
@Bean
public MyClient client1() {
return new MyClient(1);
}
?
?
@Configuration
@ConditionalOnProperty(name = "client.valid", havingValue = "true", matchIfMissing = true)
static class ClientConfiger {
?
@Bean
public MyClient client2() {
return new MyClient(2);
}
?
@Configuration
static class MyClientConfiger {
private final MyClient client;
?
@Autowired
public MyClientConfiger(MyClient client) {
this.client = client;
}
}
}
}
ConditionalBootstrap
@SpringBootApplication
public class ConditionalBootStrap {
?
public static void main(String[] args) {
SpringApplication.run(ConditionalBootStrap.class, args);
}
}
當服務啟動的時候,大家可以先猜測一下,哪些Bean會被注冊(registerBeanDefinition)?
結合源碼講解
讓我們直接達到 SpringApplication.run 中的 refreshContext 方法,看看里面做了什么?
@Override
public void refresh() throws BeansException, IllegalStateException {
...
?
try {
...
?
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
?
...
?
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
?
}
從源碼中我們看看這兩個方法主要是完成什么工作的。
invokeBeanFactoryPostProcessors
Instantiate and invoke all registered BeanFactoryPostProcessor beans, respecting explicit order if given. Must be called before singleton instantiation
這個方法主要完成BeanDefinition的工作:
- 哪些Class 實現了 registerBeanDefinition?
- BeanDefinitions 的順序是什么樣的?
提示:到refreshContext這一步之前,有些內置的類和 primarySource(即 ConditionalBootStrap)已經放到了BeanDefinitionMap中
哪些Class 實現了 registerBeanDefinition?
- 首先會將之前的BeanDefinitionNames 循環,然后找出 @Configuration Class (isFullConfigurationClass || isLiteConfigurationClass)
- 解析每一個 @Configuration 類
通過ConfigurationClassParser#parse 解析上面的configCandidates:
- 首先判斷這個類是否有效,是否應該忽略,這個地方有個很重要的提示,目前版本Spring/SpringBoot 都是通過 ConditionEvaluator#shouldSkip 來判斷一個類是否應該忽略(Determine if an item should be skipped based on {@code @Conditional} annotations)
- 該類如果不應該忽略時,就會繼續尋找該類是否有 @ComponentScan 注解(我們知道@SpringBootApplication 注解也包含 @Component注解)
- 找到 @ComponentScan 注解后,使用 ComponentScanAnnotationParser#parse 完成 basePackages 下所有類的掃描(如果@ComponentScan中沒有加basePackages,默認會使用primarySource類所在包為basePackages)
- 獲取basePackages下的所有類(getResources)
- 循環每個類,并且判斷是否是有效的沒有被排除(不在excludeFilter內)是@Component類,或者是派生類(@Configuration等)而且不應該被跳過,即 ConditionEvaluator#shouldSkip = false
- 將有效的類進行 registerBeanDefinition
到這一步時,可以猜一下上面實例中哪些類是有效的
這里你有沒有疑問:
問:為什么MyClientAutoConfiguration類是無效的?
答:因為@ConditionalOnProperty(prefix = "client", name = "enable", havingValue = "true") 不滿足條件,yml中沒有 client.enable=true,所以 ConditionEvaluator#shouldSkip=true,就被跳過了。
問:為什么內部類 MyClientConfiger 和 ClientConfiger是有效的?
答:因為這兩個類都是 @Configuration Class ,而且 ConditionEvaluator#shouldSkip=false ;@ConditionalOnProperty(matchIfMissing = true) 中 matchIfMissing=true的意思是如果沒有找到匹配的也放行。
- 獲取BeanMethod
所謂BeanMethod 就是標有@Bean的方法
上面實例中標有@Bean 的方法有:
MyClientAutoConfiguration#client1 方法 和 MyClientAutoConfiguration$ClientConfiger#client2 方法,但是因為MyClientAutoConfiguration類不符合條件,所以client1 bean method也是無效的,到這里獲取到的 BeanMethod 只有client2
將此BeanMethod進行注冊,即
registry.registerBeanDefinition(beanName, beanDefToRegister)。
到目前為止在此過程中注冊了3個BeanDefinition, 即MyClientConfiger 和 ClientConfiger ,及client2,那么MyClientConfiger 和 ClientConfiger的順序是怎么控制的?
BeanDefinitions 的順序是什么樣的?
在上面步驟中我們說了,獲取basePackages下的所有類(getResources),那么Class的順序就是在getResources 過程中改變:
protected File[] listDirectory(File dir) {
File[] files = dir.listFiles();
if (files == null) {
if (logger.isInfoEnabled()) {
logger.info("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
}
return new File[0];
}
Arrays.sort(files, Comparator.comparing(File::getName));
return files;
}
從上面源碼中我們可以看到,如果是文件,那么是根據文件名進行排序的,排序后變成
那么如果是Jar文件呢,大家可以看下源碼,是根據JarFile解壓之后的順序。
finishBeanFactoryInitialization
此方法主要完成BeanDefinition -> Bean的過程。
這里或按照順序依次遍歷之前的 BeanDefinitions,然后進行 getBean -> createBean -> initialize ,但是有個地方需要注意就是當一個Bean創建/初始化過程中如果需要/依賴其他的Bean,且這個依賴的Bean 還沒有創建的時候,則優先會創建這個Bean(doResolveDependency),比如:
@Autowired
public MyClientConfiger(MyClient client) {
this.client = client;
}
MyClientConfiger 在創建Bean初始化過程中,發現構造函數中需要依賴 MyClient 類型的Bean ,此時就會優先創建MyClient Bean ,即beanName=client2,如果沒有找到這個BeanDefinition,則此時就會報錯 throw new
NoSuchBeanDefinitionException 。
好了,Bean定義和創建的過程已經講完了,下面我拋一個問題給大家,假如我把 client.enable=true 配置加上會發生什么?
client:
id: 1
server: localhost
enable: true