提問(wèn)
- 服務(wù)啟動(dòng)的時(shí)候,SpringApplication內(nèi)部做了什么?
- 創(chuàng)建上下文的時(shí)候是使用的哪一種ApplicationContext?
- Bean 是在哪個(gè)步驟定義的,BeanDefinition怎么排序的?
- Bean 是在哪個(gè)步驟創(chuàng)建和初始化的?
- 監(jiān)聽(tīng)器有什么作用,都發(fā)布了哪些事件?
本篇文章主要介紹Bean是什么時(shí)候定義的,及Bean是如何創(chuàng)建的。
實(shí)例講解
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);
}
}
當(dāng)服務(wù)啟動(dòng)的時(shí)候,大家可以先猜測(cè)一下,哪些Bean會(huì)被注冊(cè)(registerBeanDefinition)?
結(jié)合源碼講解
讓我們直接達(dá)到 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);
?
}
從源碼中我們看看這兩個(gè)方法主要是完成什么工作的。
invokeBeanFactoryPostProcessors
Instantiate and invoke all registered BeanFactoryPostProcessor beans, respecting explicit order if given. Must be called before singleton instantiation
這個(gè)方法主要完成BeanDefinition的工作:
- 哪些Class 實(shí)現(xiàn)了 registerBeanDefinition?
- BeanDefinitions 的順序是什么樣的?
提示:到refreshContext這一步之前,有些內(nèi)置的類(lèi)和 primarySource(即 ConditionalBootStrap)已經(jīng)放到了BeanDefinitionMap中
哪些Class 實(shí)現(xiàn)了 registerBeanDefinition?
- 首先會(huì)將之前的BeanDefinitionNames 循環(huán),然后找出 @Configuration Class (isFullConfigurationClass || isLiteConfigurationClass)
- 解析每一個(gè) @Configuration 類(lèi)
通過(guò)ConfigurationClassParser#parse 解析上面的configCandidates:
- 首先判斷這個(gè)類(lèi)是否有效,是否應(yīng)該忽略,這個(gè)地方有個(gè)很重要的提示,目前版本Spring/SpringBoot 都是通過(guò) ConditionEvaluator#shouldSkip 來(lái)判斷一個(gè)類(lèi)是否應(yīng)該忽略(Determine if an item should be skipped based on {@code @Conditional} annotations)
- 該類(lèi)如果不應(yīng)該忽略時(shí),就會(huì)繼續(xù)尋找該類(lèi)是否有 @ComponentScan 注解(我們知道@SpringBootApplication 注解也包含 @Component注解)
- 找到 @ComponentScan 注解后,使用 ComponentScanAnnotationParser#parse 完成 basePackages 下所有類(lèi)的掃描(如果@ComponentScan中沒(méi)有加basePackages,默認(rèn)會(huì)使用primarySource類(lèi)所在包為basePackages)
- 獲取basePackages下的所有類(lèi)(getResources)
- 循環(huán)每個(gè)類(lèi),并且判斷是否是有效的沒(méi)有被排除(不在excludeFilter內(nèi))是@Component類(lèi),或者是派生類(lèi)(@Configuration等)而且不應(yīng)該被跳過(guò),即 ConditionEvaluator#shouldSkip = false
- 將有效的類(lèi)進(jìn)行 registerBeanDefinition
到這一步時(shí),可以猜一下上面實(shí)例中哪些類(lèi)是有效的
這里你有沒(méi)有疑問(wèn):
問(wèn):為什么MyClientAutoConfiguration類(lèi)是無(wú)效的?
答:因?yàn)锧ConditionalOnProperty(prefix = "client", name = "enable", havingValue = "true") 不滿足條件,yml中沒(méi)有 client.enable=true,所以 ConditionEvaluator#shouldSkip=true,就被跳過(guò)了。
問(wèn):為什么內(nèi)部類(lèi) MyClientConfiger 和 ClientConfiger是有效的?
答:因?yàn)檫@兩個(gè)類(lèi)都是 @Configuration Class ,而且 ConditionEvaluator#shouldSkip=false ;@ConditionalOnProperty(matchIfMissing = true) 中 matchIfMissing=true的意思是如果沒(méi)有找到匹配的也放行。
- 獲取BeanMethod
所謂BeanMethod 就是標(biāo)有@Bean的方法
上面實(shí)例中標(biāo)有@Bean 的方法有:
MyClientAutoConfiguration#client1 方法 和 MyClientAutoConfiguration$ClientConfiger#client2 方法,但是因?yàn)镸yClientAutoConfiguration類(lèi)不符合條件,所以client1 bean method也是無(wú)效的,到這里獲取到的 BeanMethod 只有client2
將此BeanMethod進(jìn)行注冊(cè),即
registry.registerBeanDefinition(beanName, beanDefToRegister)。
到目前為止在此過(guò)程中注冊(cè)了3個(gè)BeanDefinition, 即MyClientConfiger 和 ClientConfiger ,及client2,那么MyClientConfiger 和 ClientConfiger的順序是怎么控制的?
BeanDefinitions 的順序是什么樣的?
在上面步驟中我們說(shuō)了,獲取basePackages下的所有類(lèi)(getResources),那么Class的順序就是在getResources 過(guò)程中改變:
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;
}
從上面源碼中我們可以看到,如果是文件,那么是根據(jù)文件名進(jìn)行排序的,排序后變成
那么如果是Jar文件呢,大家可以看下源碼,是根據(jù)JarFile解壓之后的順序。
finishBeanFactoryInitialization
此方法主要完成BeanDefinition -> Bean的過(guò)程。
這里或按照順序依次遍歷之前的 BeanDefinitions,然后進(jìn)行 getBean -> createBean -> initialize ,但是有個(gè)地方需要注意就是當(dāng)一個(gè)Bean創(chuàng)建/初始化過(guò)程中如果需要/依賴其他的Bean,且這個(gè)依賴的Bean 還沒(méi)有創(chuàng)建的時(shí)候,則優(yōu)先會(huì)創(chuàng)建這個(gè)Bean(doResolveDependency),比如:
@Autowired
public MyClientConfiger(MyClient client) {
this.client = client;
}
MyClientConfiger 在創(chuàng)建Bean初始化過(guò)程中,發(fā)現(xiàn)構(gòu)造函數(shù)中需要依賴 MyClient 類(lèi)型的Bean ,此時(shí)就會(huì)優(yōu)先創(chuàng)建MyClient Bean ,即beanName=client2,如果沒(méi)有找到這個(gè)BeanDefinition,則此時(shí)就會(huì)報(bào)錯(cuò) throw new
NoSuchBeanDefinitionException 。
好了,Bean定義和創(chuàng)建的過(guò)程已經(jīng)講完了,下面我拋一個(gè)問(wèn)題給大家,假如我把 client.enable=true 配置加上會(huì)發(fā)生什么?
client:
id: 1
server: localhost
enable: true







