文章較長松申,建議先收藏凿叠,在較空的時(shí)候來看。
Spring Boot 框架的初衷:快速地啟動(dòng)Spring應(yīng)用
Spring Boot應(yīng)用本質(zhì)上就是一個(gè) 基于Spring框架 的應(yīng)用, 它是對 Spring" 約定優(yōu)先于配置 "理念的最佳實(shí)踐產(chǎn)物
Spring Boot最重要的四大核心:
自動(dòng)配置
起步依賴
Actuator
命令行界面
由此可見昆淡,開發(fā)人員對于Spring的理解直接決定了其對Spring Boot的理解锰瘸。故本文前面有四個(gè)小節(jié)關(guān)于Spring的基礎(chǔ)知識,分別是:
IoC容器
JavaConfig
事件監(jiān)聽
SpringFactoriesLoader詳解
如果對于這部分比較熟悉了昂灵,可以直接跳過避凝。
探索Spring IoC容器
Spring IoC容器
BeanDefinition舞萄、BeanDefinitionRegistry & BeanFactory 概念和關(guān)系
<u>BeanDefinition</u>:Bean的具體定義
<u>BeanDefinitionRegistry和BeanFactory</u>存儲了BeanDefinition:
- BeanDefinitionRegistry抽象出bean的注冊邏輯,
- BeanFactory 抽象出bean的管理邏輯恕曲,
而各個(gè)BeanFactory的實(shí)現(xiàn)類就具體承擔(dān)了bean的注冊以及管理工作鹏氧。它們之間的關(guān)系就如下圖:
DefaultListableBeanFactory作為一個(gè)比較通用的BeanFactory實(shí)現(xiàn),它同時(shí)也實(shí)現(xiàn)了BeanDefinitionRegistry接口佩谣,因此它就承擔(dān)了Bean的注冊管理工作把还。
示例代碼
//這段代碼僅為了說明BeanFactory底層的大致工作流程,實(shí)際情況會更加復(fù)雜茸俭,比如bean之間的依賴關(guān)系可能定義在外部配置文件(XML/Properties)中吊履、也可能是注解方式
// 默認(rèn)容器實(shí)現(xiàn)
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
// 根據(jù)業(yè)務(wù)對象構(gòu)造相應(yīng)的BeanDefinition
AbstractBeanDefinition definition = new RootBeanDefinition(Business.class,true);
// 將bean定義注冊到容器中
beanRegistry.registerBeanDefinition("beanName",definition);
// 如果有多個(gè)bean,還可以指定各個(gè)bean之間的依賴關(guān)系
// ........
// 然后可以從容器中獲取這個(gè)bean的實(shí)例
// 注意:這里的beanRegistry其實(shí)實(shí)現(xiàn)了BeanFactory接口调鬓,所以可以強(qiáng)轉(zhuǎn)艇炎,
// 單純的BeanDefinitionRegistry是無法強(qiáng)制轉(zhuǎn)換到BeanFactory類型的
BeanFactory container = (BeanFactory)beanRegistry;
Business business = (Business)container.getBean("beanName");
IoC容器二階段工作流程
Spring IoC容器的整個(gè)工作流程大致可以分為兩個(gè)階段。(類似于初始化和實(shí)例化)
容器啟動(dòng)階段----> 加載Configuration MetaData
BeanDefinitionReader會對加載的Configuration MetaData進(jìn)行解析和分析腾窝,并將分析后的信息組裝為相應(yīng)的BeanDefinition缀踪,最后把這些保存了bean定義的BeanDefinition健田,注冊到相應(yīng)的BeanDefinitionRegistry鹅龄,這樣容器的啟動(dòng)工作就完成了。
這個(gè)階段主要完成一些準(zhǔn)備性工作岩瘦,更側(cè)重于<u>bean對象管理信息的收集</u>循集。
當(dāng)然一些驗(yàn)證性或者輔助性的工作也在這一階段完成唇敞。
下面的代碼將模擬BeanFactory如何從xml配置文件中加載bean的定義以及依賴關(guān)系:
// 通常為BeanDefinitionRegistry的實(shí)現(xiàn)類,這里以DeFaultListabeBeanFactory為例
BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory();
// XmlBeanDefinitionReader實(shí)現(xiàn)了BeanDefinitionReader接口咒彤,用于解析XML文件
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReaderImpl(beanRegistry);
// 加載配置文件
beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");
// 從容器中獲取bean實(shí)例
BeanFactory container = (BeanFactory)beanRegistry;
Business business = (Business)container.getBean("beanName");
Bean的實(shí)例化階段
經(jīng)過第一階段疆柔,所有bean定義都通過BeanDefinition的方式注冊到BeanDefinitionRegistry中。
當(dāng)
某個(gè)請求通過容器的getBean方法請求某個(gè)對象镶柱,
-
或者因?yàn)橐蕾囮P(guān)系容器需要隱式的調(diào)用getBean旷档,
就會觸發(fā)第二階段的活動(dòng):
容器會首先檢查所請求的對象之前是否已經(jīng)實(shí)例化完成。如果沒有歇拆,則會根據(jù)注冊的BeanDefinition所提供的信息實(shí)例化被請求對象鞋屈,并為其注入依賴。當(dāng)該對象裝配完畢后查吊,容器會立即將其返回給請求方法使用谐区。
BeanFactory只是Spring IoC容器的一種實(shí)現(xiàn)湖蜕,如果沒有特殊指定逻卖,它采用采用延遲初始化策略:只有當(dāng)訪問容器中的某個(gè)對象時(shí),才對該對象進(jìn)行初始化和依賴注入操作昭抒。
另外一種使用更多的容器:ApplicationContext评也,它構(gòu)建在BeanFactory之上炼杖,屬于更高級的容器,除了具有BeanFactory的所有能力之外盗迟,還提供對事件監(jiān)聽機(jī)制以及國際化的支持等坤邪。它管理的bean,在容器啟動(dòng)時(shí)全部完成初始化和依賴注入操作罚缕。
Spring容器擴(kuò)展機(jī)制
IoC容器負(fù)責(zé)管理容器中所有bean的生命周期艇纺,而在bean生命周期的不同階段,Spring提供了不同的擴(kuò)展點(diǎn)來改變bean的命運(yùn)邮弹。
-
BeanFactoryPostProcessor
在容器實(shí)例化相應(yīng)對象之前黔衡,對beandefinition做一些額外的操作
-
BeanPostProcessor
在容器實(shí)例化相應(yīng)對象之后,對BeanDefinition做一些額外的操作
簡單的對比腌乡,BeanFactoryPostProcessor處理bean的定義盟劫,而BeanPostProcessor則處理bean完成實(shí)例化后的對象。
Bean的整個(gè)生命周期:
BeanFactoryPostProcessor
在容器的啟動(dòng)階段与纽,BeanFactoryPostProcessor允許我們在容器實(shí)例化相應(yīng)對象之前侣签,對注冊到容器的BeanDefinition所保存的信息做一些額外的操作,比如修改bean定義的某些屬性或者增加其他信息等急迂,這需要的步驟如下:
1影所、需要實(shí)現(xiàn)org.springframework.beans.factory.config.BeanFactoryPostProcessor接口,
2袋毙、需要實(shí)現(xiàn)org.springframework.core.Ordered接口型檀,以保證BeanFactoryPostProcessor按照順序執(zhí)行
BeanFactoryPostProcessor示例----PropertyPlaceholderConfigurer
Spring提供了為數(shù)不多的BeanFactoryPostProcessor實(shí)現(xiàn),我們以PropertyPlaceholderConfigurer來說明其大致的工作流程听盖。
在Spring項(xiàng)目的XML配置文件中胀溺,經(jīng)常可以看到許多配置項(xiàng)的值使用占位符皆看,而將占位符所代表的值單獨(dú)配置到獨(dú)立的properties文件仓坞,這樣可以將散落在不同XML文件中的配置集中管理,而且也方便運(yùn)維根據(jù)不同的環(huán)境進(jìn)行配置不同的值腰吟。這個(gè)非常實(shí)用的功能就是由PropertyPlaceholderConfigurer負(fù)責(zé)實(shí)現(xiàn)的无埃。
根據(jù)前文,當(dāng)BeanFactory在第一階段加載完所有配置信息時(shí)毛雇,BeanFactory中保存的對象的屬性還是以占位符方式存在的嫉称,比如${jdbc.mysql.url}。
當(dāng)PropertyPlaceholderConfigurer作為BeanFactoryPostProcessor被應(yīng)用時(shí)灵疮,它會使用properties配置文件中的值來替換相應(yīng)的BeanDefinition中占位符所表示的屬性值织阅。當(dāng)需要實(shí)例化bean時(shí),bean定義中的屬性值就已經(jīng)被替換成我們配置的值震捣。當(dāng)然其實(shí)現(xiàn)比上面描述的要復(fù)雜一些荔棉,這里僅說明其大致工作原理闹炉,更詳細(xì)的實(shí)現(xiàn)可以參考其源碼。
BeanPostProcessor
與之相似的润樱,還有BeanPostProcessor渣触,其存在于對象實(shí)例化階段。跟BeanFactoryPostProcessor類似壹若,它會處理容器內(nèi)所有符合條件并且已經(jīng)實(shí)例化后的對象嗅钻。
BeanPostProcessor定義了兩個(gè)接口:
public interface BeanPostProcessor {
// 前置處理
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
// 后置處理
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
這兩個(gè)方法中都傳入了bean對象實(shí)例的引用,為擴(kuò)展容器的對象實(shí)例化過程提供了很大便利店展,在這兒幾乎可以對傳入的實(shí)例執(zhí)行任何操作啊犬。
<u>** 注解、AOP ** 等功能的實(shí)現(xiàn)均大量使用了BeanPostProcessor壁查。</u>
BeanPostProcessor 示例一----自定義注解
比如有一個(gè)自定義注解觉至,你完全可以實(shí)現(xiàn)BeanPostProcessor的接口,在其中判斷bean對象的外部是否有該注解睡腿,如果有语御,你可以對這個(gè)bean實(shí)例執(zhí)行任何操作。
BeanPostProcessor 示例二----Aware接口
再來看一個(gè)更常見的例子席怪,在Spring中經(jīng)常能夠看到各種各樣的Aware接口应闯,其作用就是在對象實(shí)例化完成以后將Aware接口定義中規(guī)定的依賴注入到當(dāng)前實(shí)例中。比如最常見的ApplicationContextAware接口挂捻,實(shí)現(xiàn)了這個(gè)接口的類都可以獲取到一個(gè)ApplicationContext對象碉纺。
當(dāng)容器中每個(gè)對象的實(shí)例化過程走到BeanPostProcessor前置處理這一步時(shí),容器會檢測到之前注冊到容器的ApplicationContextAwareProcessor刻撒,然后就會調(diào)用其postProcessBeforeInitialization()方法骨田,檢查并設(shè)置Aware相關(guān)依賴。
// 代碼來自:org.springframework.context.support.ApplicationContextAwareProcessor
// 其postProcessBeforeInitialization方法調(diào)用了invokeAwareInterfaces方法
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
// ......
}
JavaConfig & 常見Annotation
JavaConfig
下面是使用XML配置方式來描述bean的定義:
<bean id="bookService" class="cn.moondev.service.BookServiceImpl"></bean>
而基于JavaConfig的配置形式是這樣的:
@Configuration
public class MoonBookConfiguration {
// 任何標(biāo)志了@Bean的方法声怔,其返回值將作為一個(gè)bean注冊到Spring的IOC容器中
// 方法名默認(rèn)成為該bean定義的id
@Bean
public BookService bookService() {
return new BookServiceImpl();
}
}
如果兩個(gè)bean之間有依賴關(guān)系的話态贤,在XML配置中應(yīng)該是這樣:
<bean id="bookService" class="cn.moondev.service.BookServiceImpl">
<property name="dependencyService" ref="dependencyService"/>
</bean>
<bean id="otherService" class="cn.moondev.service.OtherServiceImpl">
<property name="dependencyService" ref="dependencyService"/>
</bean>
<bean id="dependencyService" class="DependencyServiceImpl"/>
而在JavaConfig中則是這樣:
@Configuration
public class MoonBookConfiguration {
// 如果一個(gè)bean依賴另一個(gè)bean,則直接調(diào)用對應(yīng)JavaConfig類中依賴bean的創(chuàng)建方法即可
// 這里直接調(diào)用dependencyService()
@Bean
public BookService bookService() {
return new BookServiceImpl(dependencyService());
}
@Bean
public OtherService otherService() {
return new OtherServiceImpl(dependencyService());
}
@Bean
public DependencyService dependencyService() {
return new DependencyServiceImpl();
}
}
@ComponentScan
@ComponentScan
注解對應(yīng)XML配置形式中的<context:component-scan>
元素醋火,表示啟用組件掃描悠汽,Spring會自動(dòng)掃描所有通過注解配置的bean,然后將其注冊到IOC容器中芥驳。
我們可以通過basePackages
等屬性來指定@ComponentScan
自動(dòng)掃描的范圍柿冲。如果不指定,默認(rèn)從聲明@ComponentScan
所在類的package
進(jìn)行掃描兆旬。正因?yàn)槿绱思俪琒pringBoot的啟動(dòng)類都默認(rèn)在src/main/java
下。
@Import
現(xiàn)在有另外一個(gè)配置類,比如:MoonUserConfiguration
慨亲,這個(gè)配置類中有一個(gè)bean依賴于MoonBookConfiguration
中的bookService,如何將這兩個(gè)bean組合在一起宝鼓?借助@Import
即可:
@Configuration
// 可以同時(shí)導(dǎo)入多個(gè)配置類刑棵,比如:@Import({A.class,B.class})
@Import(MoonBookConfiguration.class)
public class MoonUserConfiguration {
@Bean
public UserService userService(BookService bookService) {
return new BookServiceImpl(bookService);
}
}
需要注意的是,在4.2之前愚铡,@Import
注解只支持導(dǎo)入配置類蛉签,但是在4.2之后,它支持導(dǎo)入普通類沥寥,并將這個(gè)類作為一個(gè)bean的定義注冊到IOC容器中碍舍。
@Conditional
@Conditional
注解表示<u>在滿足某種條件后才初始化一個(gè)bean或者啟用某些配置</u>。
它一般用在由@Component
邑雅、@Service
片橡、@Configuration
等注解標(biāo)識的類上面,或者由@Bean
標(biāo)記的方法上淮野。
如果一個(gè)@Configuration
類標(biāo)記了@Conditional
捧书,則該類中所有標(biāo)識了@Bean
的方法和@Import
注解導(dǎo)入的相關(guān)類將遵從這些條件。
在Spring里可以很方便的編寫你自己的條件類骤星,所要做的就是實(shí)現(xiàn)Condition
接口经瓷,并覆蓋它的matches()
方法。舉個(gè)例子洞难,下面的簡單條件類表示只有在Classpath
里存在JdbcTemplate
類時(shí)才生效:
public class JdbcTemplateCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
try {
conditionContext.getClassLoader()
.loadClass("org.springframework.jdbc.core.JdbcTemplate");
return true;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return false;
}
}
當(dāng)你用Java來聲明bean的時(shí)候舆吮,可以使用這個(gè)自定義條件類:
@Conditional(JdbcTemplateCondition.class)
@Service
public MyService service() {
......
}
這個(gè)例子中只有當(dāng)JdbcTemplateCondition
類的條件成立時(shí)才會創(chuàng)建MyService這個(gè)bean。也就是說MyService這bean的創(chuàng)建條件是classpath
里面包含JdbcTemplate
队贱,否則這個(gè)bean的聲明就會被忽略掉色冀。
Spring Boot
定義了很多有趣的條件,并把他們運(yùn)用到了配置類上柱嫌,這些配置類構(gòu)成了Spring Boot
的自動(dòng)配置的基礎(chǔ)呐伞。
Spring Boot
運(yùn)用條件化配置的方法是:定義多個(gè)特殊的條件化注解,并將它們用到配置類上慎式。下面列出了Spring Boot
提供的部分條件化注解:
條件化注解 | 配置生效條件 |
---|---|
@ConditionalOnBean | 配置了某個(gè)特定bean |
@ConditionalOnMissingBean | 沒有配置特定的bean |
@ConditionalOnClass | Classpath里有指定的類 |
@ConditionalOnMissingClass | Classpath里沒有指定的類 |
@ConditionalOnExpression | 給定的Spring Expression Language表達(dá)式計(jì)算結(jié)果為true |
@ConditionalOnJava | Java的版本匹配特定指或者一個(gè)范圍值 |
@ConditionalOnProperty | 指定的配置屬性要有一個(gè)明確的值 |
@ConditionalOnResource | Classpath里有指定的資源 |
@ConditionalOnWebApplication | 這是一個(gè)Web應(yīng)用程序 |
@ConditionalOnNotWebApplication | 這不是一個(gè)Web應(yīng)用程序 |
@ConfigurationProperties與@EnableConfigurationProperties
當(dāng)某些屬性的值需要配置的時(shí)候:
我們一般會在application.properties
文件中新建配置項(xiàng)伶氢,然后在bean中使用@Value
注解來獲取配置的值
// jdbc config
jdbc.mysql.url=jdbc:mysql://localhost:3306/sampledb
jdbc.mysql.username=root
jdbc.mysql.password=123456
......
// 配置數(shù)據(jù)源
@Configuration
public class HikariDataSourceConfiguration {
@Value("jdbc.mysql.url")
public String url;
@Value("jdbc.mysql.username")
public String user;
@Value("jdbc.mysql.password")
public String password;
@Bean
public HikariDataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(url);
hikariConfig.setUsername(user);
hikariConfig.setPassword(password);
// 省略部分代碼
return new HikariDataSource(hikariConfig);
}
}
以下方式更為得優(yōu)雅,
@Component
// 還可以通過@PropertySource("classpath:jdbc.properties")來指定配置文件
@ConfigurationProperties("jdbc.mysql")
// 前綴=jdbc.mysql瘪吏,會在配置文件中尋找jdbc.mysql.*的配置項(xiàng)
pulic class JdbcConfig {
public String url;
public String username;
public String password;
}
@Configuration
public class HikariDataSourceConfiguration {
@AutoWired
public JdbcConfig config;
@Bean
public HikariDataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(config.url);
hikariConfig.setUsername(config.username);
hikariConfig.setPassword(config.password);
// 省略部分代碼
return new HikariDataSource(hikariConfig);
}
}
@ConfigurationProperties對于更為復(fù)雜的配置癣防,處理起來也是得心應(yīng)手,比如有如下配置文件:
#App
app.menus[0].title=Home
app.menus[0].name=Home
app.menus[0].path=/
app.menus[1].title=Login
app.menus[1].name=Login
app.menus[1].path=/login
app.compiler.timeout=5
app.compiler.output-folder=/temp/
app.error=/error/
可以定義如下配置類來接收這些屬性
@Component
@ConfigurationProperties("app")
public class AppProperties {
public String error;
public List<Menu> menus = new ArrayList<>();
public Compiler compiler = new Compiler();
public static class Menu {
public String name;
public String path;
public String title;
}
public static class Compiler {
public String timeout;
public String outputFolder;
}
}
SpringFactoriesLoader詳解
JVM三種類加載器
- BootStrapClassLoader ——> Java核心類庫
- ExtClassLoader ——> Java擴(kuò)展類庫
- AppClassLoader ——> 應(yīng)用的類路徑CLASSPATH下的類庫
雙親委派模型簡介
JVM通過雙親委派模型進(jìn)行類的加載掌眠,我們也可以通過繼承java.lang.classloader實(shí)現(xiàn)自己的類加載器蕾盯。
當(dāng)一個(gè)類加載器收到類加載任務(wù)時(shí),會先交給自己的父加載器去完成蓝丙,因此最終加載任務(wù)都會傳遞到最頂層的BootstrapClassLoader级遭,只有當(dāng)父加載器無法完成加載任務(wù)時(shí)望拖,才會嘗試自己來加載。
采用雙親委派模型的一個(gè)好處是保證使用不同類加載器最終得到的都是同一個(gè)對象挫鸽。
protected Class<?> loadClass(String name, boolean resolve) {
synchronized (getClassLoadingLock(name)) {
// 首先说敏,檢查該類是否已經(jīng)被加載,如果從JVM緩存中找到該類丢郊,則直接返回
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 遵循雙親委派的模型盔沫,首先會通過遞歸從父加載器開始找,
// 直到父類加載器是BootstrapClassLoader為止
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {}
if (c == null) {
// 如果還找不到枫匾,嘗試通過findClass方法去尋找
// findClass是留給開發(fā)者自己實(shí)現(xiàn)的架诞,也就是說
// 自定義類加載器時(shí),重寫此方法即可
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
雙親委派弊端
但雙親委派模型并不能解決所有的類加載器問題干茉。
比如谴忧,Java 提供了很多服務(wù)提供者接口(Service Provider Interface,SPI)角虫,允許第三方為這些接口提供實(shí)現(xiàn)俏蛮。常見的 SPI 有 JDBC、JNDI上遥、JAXP 等搏屑。
SPI的接口由核心類庫提供,卻由第三方實(shí)現(xiàn)粉楚,這樣就存在一個(gè)問題:SPI 的接口是 Java 核心庫的一部分辣恋,是由BootstrapClassLoader加載的;SPI實(shí)現(xiàn)的Java類一般是由AppClassLoader來加載的模软。BootstrapClassLoader是無法找到 SPI 的實(shí)現(xiàn)類的伟骨,因?yàn)樗患虞dJava的核心庫。它也不能代理給AppClassLoader燃异,因?yàn)樗亲铐攲拥念惣虞d器携狭。也就是說,雙親委派模型并不能解決這個(gè)問題回俐。
雙親委派弊端解決方案ContextClassLoader
線程上下文類加載器(ContextClassLoader)正好解決了這個(gè)問題逛腿。從名稱上看,可能會誤解為它是一種新的類加載器仅颇,實(shí)際上单默,它僅僅是Thread類的一個(gè)變量而已,可以通過setContextClassLoader(ClassLoader cl)和getContextClassLoader()來設(shè)置和獲取該對象忘瓦。
如果不做任何的設(shè)置搁廓,Java應(yīng)用的線程的上下文類加載器默認(rèn)就是AppClassLoader。在核心類庫使用SPI接口時(shí),傳遞的類加載器使用線程上下文類加載器境蜕,就可以成功的加載到SPI實(shí)現(xiàn)的類蝙场。線程上下文類加載器在很多SPI的實(shí)現(xiàn)中都會用到。但在JDBC中粱年,你可能會看到一種更直接的實(shí)現(xiàn)方式售滤,比如,JDBC驅(qū)動(dòng)管理java.sql.Driver中的loadInitialDrivers()方法中逼泣,你可以直接看到JDK是如何加載驅(qū)動(dòng)的:
for (String aDriver : driversList) {
try {
// 直接使用AppClassLoader
Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
其實(shí)講解線程上下文類加載器,最主要是讓大家在看到Thread.currentThread().getClassLoader()
和Thread.currentThread().getContextClassLoader()
時(shí)不會一臉懵逼舟舒,這兩者除了在許多底層框架中取得的ClassLoader可能會有所不同外诅福,其他大多數(shù)業(yè)務(wù)場景下都是一樣的骡和。
類加載器的另外一個(gè)重要功能:加載資源
類加載器除了加載class外,還有一個(gè)非常重要功能,就是加載資源恬砂,它可以從jar包中讀取任何資源文件,比如憋槐,ClassLoader.getResources(String name)方法就是用于讀取jar包中的資源文件姚建,其代碼如下:
public Enumeration<URL> getResources(String name) throws IOException {
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
if (parent != null) {
tmp[0] = parent.getResources(name);
} else {
tmp[0] = getBootstrapResources(name);
}
//findResources(name)方法會遍歷其負(fù)責(zé)加載的所有jar包,找到j(luò)ar包中名稱為name的資源文件币励,這里的資源可以是任何文件慷蠕,甚至是.class文件
tmp[1] = findResources(name);
return new CompoundEnumeration<>(tmp);
}
它的邏輯其實(shí)跟類加載的邏輯是一樣的,首先判斷父類加載器是否為空食呻,不為空則委托父類加載器執(zhí)行資源查找任務(wù)流炕,直到BootstrapClassLoader,最后才輪到自己查找仅胞。而不同的類加載器負(fù)責(zé)掃描不同路徑下的jar包每辟,就如同加載class一樣,最后會掃描所有的jar包干旧,找到符合條件的資源文件渠欺。
下面的示例,用于查找Array.class文件:
// 尋找Array.class文件
public static void main(String[] args) throws Exception{
// Array.class的完整路徑
String name = "java/sql/Array.class";
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
System.out.println(url.toString());
}
}
運(yùn)行后可以得到如下結(jié)果:
$JAVA_HOME/jre/lib/rt.jar!/java/sql/Array.class
根據(jù)資源文件的URL椎眯,可以構(gòu)造相應(yīng)的文件來讀取資源內(nèi)容挠将。
詳解SpringFactoriesLoader源碼
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// spring.factories文件的格式為:key=value1,value2,value3
// 從所有的jar包中找到META-INF/spring.factories文件
// 然后從文件中解析出key=factoryClass類名稱的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 取得資源文件的URL
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
// 遍歷所有的URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 根據(jù)資源文件URL解析properties文件
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
// 組裝數(shù)據(jù),并返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
有了前面關(guān)于ClassLoader的知識编整,再來理解這段代碼:從CLASSPATH下的每個(gè)Jar包中搜尋所有META-INF/spring.factories配置文件捐名,然后將解析properties文件,找到指定名稱的配置后返回闹击。需要注意的是镶蹋,其實(shí)這里不僅僅是會去ClassPath路徑下查找,會掃描所有路徑下的Jar包,只不過這個(gè)文件只會在Classpath下的jar包中贺归。來簡單看下spring.factories文件的內(nèi)容吧:
// 來自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
// EnableAutoConfiguration后文會講到淆两,它用于開啟Spring Boot自動(dòng)配置功能
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\
執(zhí)行l(wèi)oadFactoryNames(EnableAutoConfiguration.class, classLoader)后,得到對應(yīng)的一組@Configuration類拂酣,我們就可以通過反射實(shí)例化這些類然后注入到IOC容器中秋冰,最后容器里就有了一系列標(biāo)注了@Configuration的JavaConfig形式的配置類。
這就是SpringFactoriesLoader婶熬,它本質(zhì)上屬于Spring框架私有的一種擴(kuò)展方案剑勾,類似于SPI,Spring Boot在Spring基礎(chǔ)上的很多核心功能都是基于此赵颅。
Spring容器的事件監(jiān)聽機(jī)制
在服務(wù)器端虽另,事件的監(jiān)聽機(jī)制更多的用于異步通知以及監(jiān)控和異常處理。
Java提供了實(shí)現(xiàn)事件監(jiān)聽機(jī)制的兩個(gè)基礎(chǔ)類:
- java.util.EventObject 自定義事件類型
- java.util.EventListener 事件的監(jiān)聽器
同時(shí)還需要注意 EventPublisher 事件發(fā)布者
首先定義事件類型饺谬,通常的做法是擴(kuò)展EventObject捂刺,隨著事件的發(fā)生,相應(yīng)的狀態(tài)通常都封裝在此類中:
public class MethodMonitorEvent extends EventObject {
// 時(shí)間戳募寨,用于記錄方法開始執(zhí)行的時(shí)間
public long timestamp;
public MethodMonitorEvent(Object source) {
super(source);
}
}
事件發(fā)布之后族展,相應(yīng)的監(jiān)聽器即可對該類型的事件進(jìn)行處理,我們可以在方法開始執(zhí)行之前發(fā)布一個(gè)begin事件拔鹰,在方法執(zhí)行結(jié)束之后發(fā)布一個(gè)end事件仪缸,相應(yīng)地,事件監(jiān)聽器需要提供方法對這兩種情況下接收到的事件進(jìn)行處理:
// 1列肢、定義事件監(jiān)聽接口
public interface MethodMonitorEventListener extends EventListener {
// 處理方法執(zhí)行之前發(fā)布的事件
public void onMethodBegin(MethodMonitorEvent event);
// 處理方法結(jié)束時(shí)發(fā)布的事件
public void onMethodEnd(MethodMonitorEvent event);
}
// 2腹殿、事件監(jiān)聽接口的實(shí)現(xiàn):如何處理
public class AbstractMethodMonitorEventListener implements MethodMonitorEventListener {
@Override
public void onMethodBegin(MethodMonitorEvent event) {
// 記錄方法開始執(zhí)行時(shí)的時(shí)間
event.timestamp = System.currentTimeMillis();
}
@Override
public void onMethodEnd(MethodMonitorEvent event) {
// 計(jì)算方法耗時(shí)
long duration = System.currentTimeMillis() - event.timestamp;
System.out.println("耗時(shí):" + duration);
}
}
事件監(jiān)聽器接口針對不同的事件發(fā)布實(shí)際提供相應(yīng)的處理方法定義,最重要的是例书,其方法只接收MethodMonitorEvent參數(shù)锣尉,說明這個(gè)監(jiān)聽器類只負(fù)責(zé)監(jiān)聽器對應(yīng)的事件并進(jìn)行處理。
有了事件和監(jiān)聽器决采,剩下的就是發(fā)布事件自沧,然后讓相應(yīng)的監(jiān)聽器監(jiān)聽并處理。通常情況树瞭,我們會有一個(gè)事件發(fā)布者拇厢,它本身作為事件源,在合適的時(shí)機(jī)晒喷,將相應(yīng)的事件發(fā)布給對應(yīng)的事件監(jiān)聽器:
public class MethodMonitorEventPublisher {
private List<MethodMonitorEventListener> listeners = new ArrayList<MethodMonitorEventListener>();
public void methodMonitor() {
MethodMonitorEvent eventObject = new MethodMonitorEvent(this);
publishEvent("begin",eventObject);
// 模擬方法執(zhí)行:休眠5秒鐘
TimeUnit.SECONDS.sleep(5);
publishEvent("end",eventObject);
}
private void publishEvent(String status,MethodMonitorEvent event) {
// 避免在事件處理期間孝偎,監(jiān)聽器被移除,這里為了安全做一個(gè)復(fù)制操作
List<MethodMonitorEventListener> copyListeners = new ArrayList<MethodMonitorEventListener>(listeners);
for (MethodMonitorEventListener listener : copyListeners) {
if ("begin".equals(status)) {
listener.onMethodBegin(event);
} else {
listener.onMethodEnd(event);
}
}
}
public static void main(String[] args) {
MethodMonitorEventPublisher publisher = new MethodMonitorEventPublisher();
publisher.addEventListener(new AbstractMethodMonitorEventListener());
publisher.methodMonitor();
}
// 省略實(shí)現(xiàn)
public void addEventListener(MethodMonitorEventListener listener) {}
public void removeEventListener(MethodMonitorEventListener listener) {}
public void removeAllListeners() {}
}
對于事件發(fā)布者(事件源)通常需要關(guān)注兩點(diǎn):
1凉敲、在合適的時(shí)機(jī)發(fā)布事件衣盾。此例中的methodMonitor()方法是事件發(fā)布的源頭寺旺,其在方法執(zhí)行之前和結(jié)束之后兩個(gè)時(shí)間點(diǎn)發(fā)布MethodMonitorEvent事件,每個(gè)時(shí)間點(diǎn)發(fā)布的事件都會傳給相應(yīng)的監(jiān)聽器進(jìn)行處理势决。在具體實(shí)現(xiàn)時(shí)需要注意的是阻塑,事件發(fā)布是順序執(zhí)行,為了不影響處理性能果复,事件監(jiān)聽器的處理邏輯應(yīng)盡量簡單陈莽。
2、事件監(jiān)聽器的管理虽抄。publisher類中提供了事件監(jiān)聽器的注冊與移除方法走搁,這樣客戶端可以根據(jù)實(shí)際情況決定是否需要注冊新的監(jiān)聽器或者移除某個(gè)監(jiān)聽器。如果這里沒有提供remove方法迈窟,那么注冊的監(jiān)聽器示例將一直被MethodMonitorEventPublisher引用私植,即使已經(jīng)廢棄不用了,也依然在發(fā)布者的監(jiān)聽器列表中菠隆,這會導(dǎo)致隱性的內(nèi)存泄漏兵琳。
Spring容器內(nèi)的事件監(jiān)聽機(jī)制
Spring的ApplicationContext容器內(nèi)部中(以下兩者皆以bean的形式注冊在容器中):
所有事件類型均繼承自org.springframework.context.AppliationEvent(繼承自EventObject)
所有監(jiān)聽器都實(shí)現(xiàn)org.springframework.context.ApplicationListener(繼承自EventListener)
一旦在容器內(nèi)發(fā)布ApplicationEvent及其子類型的事件狂秘,注冊到容器的ApplicationListener就會對這些事件進(jìn)行處理骇径。
Spring提供了ApplicationEvent接口的一些默認(rèn)的實(shí)現(xiàn),比如:
- ContextClosedEvent 表示容器在即將關(guān)閉時(shí)發(fā)布的事件類型
- ContextRefreshedEvent 表示容器在初始化或者刷新的時(shí)候發(fā)布的事件類型
ApplicationContext容器在啟動(dòng)時(shí)者春,會自動(dòng)識別并加載EventListener類型的bean破衔,一旦容器內(nèi)有事件發(fā)布,將通知這些注冊到容器的EventListener钱烟。
ApplicationContext接口繼承了ApplicationEventPublisher接口晰筛,該接口提供了void publishEvent(ApplicationEvent event)方法定義,不難看出拴袭,ApplicationContext容器擔(dān)當(dāng)?shù)木褪鞘录l(fā)布者的角色读第。
可以查看AbstractApplicationContext.publishEvent(ApplicationEvent event)方法的源碼:ApplicationContext將事件的發(fā)布以及監(jiān)聽器的管理工作委托給ApplicationEventMulticaster接口的實(shí)現(xiàn)類。在容器啟動(dòng)時(shí)拥刻,會檢查容器內(nèi)是否存在名為applicationEventMulticaster的ApplicationEventMulticaster對象實(shí)例怜瞒。如果有就使用其提供的實(shí)現(xiàn),沒有就默認(rèn)初始化一個(gè)SimpleApplicationEventMulticaster作為實(shí)現(xiàn)般哼。
最后吴汪,如果我們業(yè)務(wù)需要在容器內(nèi)部發(fā)布事件,只需要為其注入ApplicationEventPublisher依賴即可:實(shí)現(xiàn)ApplicationEventPublisherAware接口或者ApplicationContextAware接口蒸眠。
揭秘自動(dòng)配置原理
@SpringBootApplication
public class MoonApplication {
public static void main(String[] args) {
SpringApplication.run(MoonApplication.class, args);
}
}
@SpringBootApplication開啟組件掃描和自動(dòng)配置
@SpringApplication.run負(fù)責(zé)啟動(dòng)引導(dǎo)應(yīng)用程序
@SpringBootApplication是一個(gè)復(fù)合的注解漾橙,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// ......
}
以下三個(gè)注解起到關(guān)鍵作用:
- @SpringBootConfiguration
- 就是@Configuration,它是Spring框架的注解楞卡,標(biāo)明該類是一個(gè)JavaConfig配置類
- @ComponentScan
- 啟用組件掃描
- @EnableAutoConfiguration
- 就是開啟Spring Boot自動(dòng)配置功能霜运,Spring Boot會根據(jù)應(yīng)用的依賴脾歇、自定義的bean、classpath下有沒有某個(gè)類 等等因素來猜測你需要的bean觉渴,然后注冊到IoC容器中介劫。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
//@Import注解用于導(dǎo)入類,并將EnableAutoConfigurationImportSelector作為一個(gè)bean的定義注冊到容器中
//EnableAutoConfigurationImportSelector會將所有符合條件的@Configuration配置都加載到容器中
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// ......
}
//EnableAutoConfigurationImportSelector.class
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 省略了大部分代碼案淋,保留一句核心代碼
// 注意:SpringBoot最近版本中座韵,這句代碼被封裝在一個(gè)單獨(dú)的方法中
List<String> factories = new ArrayList<String>(new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader)));
}
這個(gè)類會掃描所有的jar包,將所有符合條件的@Configuration配置類注入的容器中踢京,何為符合條件誉碴,看看META-INF/spring.factories的文件內(nèi)容:
// 來自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
// 配置的key = EnableAutoConfiguration,與代碼中一致
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\
.....
以DataSourceAutoConfiguration為例瓣距,看看Spring Boot是如何自動(dòng)配置的:
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration {
}
分別說一說:
- @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }):當(dāng)Classpath中存在DataSource或者EmbeddedDatabaseType類時(shí)才啟用這個(gè)配置黔帕,否則這個(gè)配置將被忽略。
- @EnableConfigurationProperties(DataSourceProperties.class):將DataSource的默認(rèn)配置類注入到IOC容器中蹈丸,DataSourceproperties定義為:
// 提供對datasource配置信息的支持成黄,所有的配置前綴為:spring.datasource
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
private ClassLoader classLoader;
private Environment environment;
private String name = "testdb";
......
}
- @Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class }):導(dǎo)入其他額外的配置,就以DataSourcePoolMetadataProvidersConfiguration為例吧逻杖。
@Configuration
public class DataSourcePoolMetadataProvidersConfiguration {
@Configuration
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
static class TomcatDataSourcePoolMetadataProviderConfiguration {
@Bean
public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() {
.....
}
}
......
}
DataSourcePoolMetadataProvidersConfiguration是數(shù)據(jù)庫連接池提供者的一個(gè)配置類奋岁,即Classpath中存在org.apache.tomcat.jdbc.pool.DataSource.class,則使用tomcat-jdbc連接池荸百,如果Classpath中存在HikariDataSource.class則使用Hikari連接池闻伶。
以上足以說明Spring Boot如何利用條件話配置來實(shí)現(xiàn)自動(dòng)配置的。
@EnableAutoConfiguration中導(dǎo)入了EnableAutoConfigurationImportSelector類够话,而這個(gè)類的selectImports()通過SpringFactoriesLoader得到了大量的配置類蓝翰,而每一個(gè)配置類則根據(jù)條件化配置來做出決策,以實(shí)現(xiàn)自動(dòng)配置女嘲。
整個(gè)流程很清晰畜份,但EnableAutoConfigurationImportSelector.selectImports()是何時(shí)執(zhí)行的?
其實(shí)EnableAutoConfigurationImportSelector.selectImports()會在容器啟動(dòng)過程( AbstractApplicationContext.refresh() )中執(zhí)行欣尼。
Spring Boot 應(yīng)用啟動(dòng)的秘密
SpringBoot整個(gè)啟動(dòng)流程分為兩個(gè)步驟:
- SpringApplication對象初始化
- 執(zhí)行該對象的run方法
@SpringBootApplication
public class test {
public static void main(String[] args) {
//SpringApplication.run()方法所返回的Context爆雹,可以從中取出一些Bean 來進(jìn)行其它的一些操作
//ConfigurableApplicationContext context =
// SpringApplication.run(DtsServerApp.class, args);
//NettyServerController controller = context.getBean(NettyServerController.class);
//controller.start();
SpringApplication.run(test.class,args);
}
}
SpringApplication初始化
看下SpringApplication的初始化流程,SpringApplication的構(gòu)造方法中調(diào)用initialize(Object[] sources)方法媒至,其代碼如下:
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[]{source}, args);
}
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return (new SpringApplication(sources)).run(args);
}
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param sources the bean sources
* @see #run(Object, String[])
* @see #SpringApplication(ResourceLoader, Object...)
*/
public SpringApplication(Object... sources) {
initialize(sources);
}
private void initialize(Object[] sources) {
// 上下文配置源保存在set中
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
// 推導(dǎo)是否是web應(yīng)用
this.webEnvironment = deduceWebEnvironment();
/*
* TWO IMPORTANT STEPS:
*/
// 從 META-INF/spring.factories 中加載應(yīng)用上下文初始化器類
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 從 META-INF/spring.factories 中加載事件監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推導(dǎo)出主類顶别,
this.mainApplicationClass = deduceMainApplicationClass();
}
初始化流程中最重要的就是通過SpringFactoriesLoader找到spring.factories文件中配置的以下兩個(gè)接口的實(shí)現(xiàn)類名稱,以便后期構(gòu)造相應(yīng)的實(shí)例
TWO IMPORTANT INTERFACE:
- ApplicationContextInitializer
- ApplicationListener
1拒啰、ApplicationContextInitializer的主要目的是在ConfigurableApplicationContext做refresh之前驯绎,對ConfigurableApplicationContext實(shí)例做進(jìn)一步的設(shè)置或處理。
ConfigurableApplicationContext繼承自ApplicationContext谋旦,其主要提供了對ApplicationContext進(jìn)行設(shè)置的能力剩失。
2屈尼、ApplicationListener是Spring框架對Java事件監(jiān)聽機(jī)制的一種框架實(shí)現(xiàn)。
如果想為Spring Boot應(yīng)用添加監(jiān)聽器拴孤,該如何實(shí)現(xiàn)脾歧?
Spring Boot提供兩種方式來添加自定義監(jiān)聽器:
- 通過SpringApplication.addListeners(ApplicationListener<?>... listeners)
- SpringApplication.setListeners(Collection<? extends ApplicationListener<?>> listeners)兩個(gè)方法來添加一個(gè)或者多個(gè)自定義監(jiān)聽器
- @Listener(todo)
- META-INF/spring.factories文件中新增配置
既然SpringApplication的初始化流程中已經(jīng)從spring.factories中獲取到ApplicationListener的實(shí)現(xiàn)類,那么我們直接在自己的jar包的META-INF/spring.factories文件中新增配置即可:
org.springframework.context.ApplicationListener=\
cn.moondev.listeners.xxxxListener\
關(guān)于SpringApplication的初始化演熟,我們就說這么多鞭执。
SpringApplication運(yùn)行
- 運(yùn)行環(huán)境/參數(shù)設(shè)置 -> 打印Banner -> 創(chuàng)建ApplicationContext -> 上下文準(zhǔn)備 -> 上下文刷新
- ------------------------------------------ 運(yùn)行監(jiān)視器 -----------------------------------
Spring Boot應(yīng)用的整個(gè)啟動(dòng)流程都封裝在SpringApplication.run方法中,
其本質(zhì)上就是在Spring容器啟動(dòng)的基礎(chǔ)上做了大量的擴(kuò)展芒粹,按照這個(gè)思路來看看源碼:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
// ① 獲取SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// ② 運(yùn)行參數(shù)保存在ApplicationArguments中
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 配置環(huán)境屬性兄纺,同時(shí)會觸發(fā) ApplicationEnvironmentPreparedEvent 事件
ConfigurableEnvironment environment =
prepareEnvironment(listeners,applicationArguments);
// ③
Banner printedBanner = printBanner(environment);
// ④ 創(chuàng)建具體的 ApplicationContext,
// 根據(jù)是否為 web 環(huán)境分別為:AnnotationConfigEmbeddedWebApplicationContext化漆、
// AnnotationConfigApplicationContext
context = createApplicationContext();
// ⑤ 失敗分析估脆,錯(cuò)誤診斷
analyzers = new FailureAnalyzers(context);
// ⑥ 上下文準(zhǔn)備
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
// ⑦ 刷新上下文
refreshContext(context);
// ⑧
afterRefresh(context, applicationArguments);
// ⑨ 啟動(dòng)完成
listeners.finished(context, null);
stopWatch.stop();
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
① 通過SpringFactoriesLoader查找并加載所有的SpringApplicationRunListeners,通過調(diào)用starting()方法通知所有的SpringApplicationRunListeners:應(yīng)用開始啟動(dòng)了座云。
SpringApplicationRunListeners其本質(zhì)上就是一個(gè)事件發(fā)布者疙赠,它在SpringBoot應(yīng)用啟動(dòng)的不同時(shí)間點(diǎn)發(fā)布不同應(yīng)用事件類型(ApplicationEvent),如果有哪些事件監(jiān)聽者(ApplicationListener)對這些事件感興趣朦拖,則可以接收并且處理圃阳。還記得初始化流程中,SpringApplication加載了一系列ApplicationListener嗎贞谓?這個(gè)啟動(dòng)流程中沒有發(fā)現(xiàn)有發(fā)布事件的代碼限佩,其實(shí)都已經(jīng)在SpringApplicationRunListeners這兒實(shí)現(xiàn)了葵诈。
簡單的分析一下其實(shí)現(xiàn)流程裸弦,首先看下SpringApplicationRunListener的源碼:
public interface SpringApplicationRunListener {
// 運(yùn)行run方法時(shí)立即調(diào)用此方法,可以用戶非常早期的初始化工作
void starting();
// Environment準(zhǔn)備好后作喘,并且ApplicationContext創(chuàng)建之前調(diào)用
void environmentPrepared(ConfigurableEnvironment environment);
// ApplicationContext創(chuàng)建好后立即調(diào)用
void contextPrepared(ConfigurableApplicationContext context);
// ApplicationContext加載完成理疙,在refresh之前調(diào)用
void contextLoaded(ConfigurableApplicationContext context);
// 當(dāng)run方法結(jié)束之前調(diào)用
void finished(ConfigurableApplicationContext context, Throwable exception);
}
SpringApplicationRunListener只有一個(gè)實(shí)現(xiàn)類:EventPublishingRunListener。①處的代碼只會獲取到一個(gè)EventPublishingRunListener的實(shí)例泞坦,我們來看看starting()方法的內(nèi)容:
public void starting() {
// 發(fā)布一個(gè)ApplicationStartedEvent
this.initialMulticaster.multicastEvent(
new ApplicationStartedEvent(this.application, this.args));
}
順著這個(gè)邏輯窖贤,你可以在②處的prepareEnvironment()方法的源碼中找到listeners.environmentPrepared(environment);即SpringApplicationRunListener接口的第二個(gè)方法,那不出你所料贰锁,environmentPrepared()又發(fā)布了另外一個(gè)事件ApplicationEnvironmentPreparedEvent赃梧。
② 創(chuàng)建并配置當(dāng)前應(yīng)用將要使用的Environment.
Environment用于描述應(yīng)用程序當(dāng)前的運(yùn)行環(huán)境,其抽象了兩個(gè)方面的內(nèi)容:
- 配置文件(profile)
- 屬性(properties)
不同的環(huán)境(eg:生產(chǎn)環(huán)境豌熄、預(yù)發(fā)布環(huán)境)可以使用不同的配置文件授嘀,而屬性則可以從<u>配置文件、環(huán)境變量锣险、命令行參數(shù)</u>等來源獲取蹄皱。
因此览闰,當(dāng)Environment準(zhǔn)備好后,在整個(gè)應(yīng)用的任何時(shí)候巷折,都可以從Environment中獲取資源压鉴。
總結(jié)起來,②處的兩句代碼锻拘,主要完成以下幾件事:
- 判斷Environment是否存在油吭,不存在就創(chuàng)建(如果是web項(xiàng)目就創(chuàng)建StandardServletEnvironment,否則創(chuàng)建StandardEnvironment)
- 配置Environment:配置profile以及properties
- 調(diào)用SpringApplicationRunListener的environmentPrepared()方法署拟,通知事件監(jiān)聽者:應(yīng)用的Environment已經(jīng)準(zhǔn)備好
③上鞠、SpringBoot應(yīng)用在啟動(dòng)時(shí)會輸出這樣的東西:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.6.RELEASE)
④、根據(jù)是否是web項(xiàng)目芯丧,來創(chuàng)建不同的ApplicationContext容器芍阎。
⑤、創(chuàng)建一系列FailureAnalyzer缨恒,創(chuàng)建流程依然是通過SpringFactoriesLoader獲取到所有實(shí)現(xiàn)FailureAnalyzer接口的class谴咸,然后在創(chuàng)建對應(yīng)的實(shí)例。FailureAnalyzer用于分析故障并提供相關(guān)診斷信息骗露。
⑥岭佳、初始化ApplicationContext,主要完成以下工作:
-
a) 將準(zhǔn)備好的Environment設(shè)置給ApplicationContext
b) 遍歷調(diào)用所有的ApplicationContextInitializer的initialize()方法來對已經(jīng)創(chuàng)建好的ApplicationContext進(jìn)行進(jìn)一步的處理
調(diào)用SpringApplicationRunListener的contextPrepared()方法萧锉,通知所有的監(jiān)聽者:ApplicationContext已經(jīng)準(zhǔn)備完畢
-
將所有的bean加載到容器中
調(diào)用SpringApplicationRunListener的contextLoaded()方法珊随,通知所有的監(jiān)聽者:ApplicationContext已經(jīng)裝載完畢private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { //a) context.setEnvironment(environment); // ApplicationContext 創(chuàng)建后的處理擴(kuò)展點(diǎn):可以設(shè)置 BeanNameGenerator 和 ResourceLoader postProcessApplicationContext(context); //b) applyInitializers(context); // “應(yīng)用上下文已準(zhǔn)備好” 事件,默認(rèn)實(shí)現(xiàn)空 listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans //注冊Spring boot 特有的單例 bean context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { context.getBeanFactory().registerSingleton("springBootBanner", printedBanner); } // Load the sources Set<Object> sources = getSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[sources.size()])); // 上下文加載完成柿隙,觸發(fā) ApplicationPreparedEvent 事件 listeners.contextLoaded(context); }
/**
* Apply any {@link ApplicationContextInitializer}s to the context before it is
* refreshed.
* @param context the configured ApplicationContext (not refreshed yet)
* @see ConfigurableApplicationContext#refresh()
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
⑦叶洞、這個(gè)過程和普通 Spring 應(yīng)用是一樣的( org.springframework.context.support.AbstractApplicationContext#refresh),實(shí)現(xiàn)對 bean 的加載初始化禀崖。
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
/**
* Refresh the underlying {@link ApplicationContext}.
* @param applicationContext the application context to refresh
*/
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
調(diào)用ApplicationContext的refresh()方法衩辟,完成IoC容器可用的最后一道工序雷激。從名字上理解為刷新容器汤功,那何為刷新?就是插手容器的啟動(dòng)魔种,聯(lián)系一下第一小節(jié)的內(nèi)容掸屡。那如何刷新呢封寞?且看下面代碼:
// 摘自refresh()方法中一句代碼
invokeBeanFactoryPostProcessors(beanFactory);
看看這個(gè)方法的實(shí)現(xiàn):
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
......
}
獲取到所有的BeanFactoryPostProcessor來對容器做一些額外的操作。BeanFactoryPostProcessor允許我們在容器實(shí)例化相應(yīng)對象之前仅财,對注冊到容器的BeanDefinition所保存的信息做一些額外的操作狈究。
這里的getBeanFactoryPostProcessors()方法可以獲取到3個(gè)Processor:
- ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
- SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
- ConfigFileApplicationListener$PropertySourceOrderingPostProcessor
不是有那么多BeanFactoryPostProcessor的實(shí)現(xiàn)類,為什么這兒只有這3個(gè)满着?因?yàn)樵诔跏蓟鞒太@取到的各種ApplicationContextInitializer和ApplicationListener中谦炒,只有上文3個(gè)做了類似于如下操作:
public void initialize(ConfigurableApplicationContext context) {
context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
}
然后進(jìn)入到PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()方法
這個(gè)方法除了會遍歷上面的3個(gè)BeanFactoryPostProcessor處理外贯莺,還會獲取類型為BeanDefinitionRegistryPostProcessor的bean:org.springframework.context.annotation.internalConfigurationAnnotationProcessor,對應(yīng)的Class為ConfigurationClassPostProcessor宁改。
ConfigurationClassPostProcessor用于解析處理各種注解缕探,包括:@Configuration、@ComponentScan还蹲、@Import爹耗、@PropertySource、@ImportResource谜喊、@Bean潭兽。當(dāng)處理@import注解的時(shí)候,就會調(diào)用<自動(dòng)配置>這一小節(jié)中的EnableAutoConfigurationImportSelector.selectImports()來完成自動(dòng)配置功能斗遏。
⑧山卦、查找當(dāng)前context中是否注冊有CommandLineRunner和ApplicationRunner,如果有則遍歷執(zhí)行它們诵次。
SpringApplication啟動(dòng)后账蓉,如果我們想實(shí)現(xiàn)自己的邏輯,便可以通過實(shí)現(xiàn) ApplicationRunner 或 CommandLineRunner 來實(shí)現(xiàn)(二者的唯一區(qū)別是參數(shù)形態(tài))逾一。這兩種 Runner 的生效點(diǎn)便是上下文 refresh 完成后铸本。
/**
* Called after the context has been refreshed.
* @param context the application context
* @param args the application arguments
*/
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
callRunners(context, args);
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<Object>();
// 二者的順序
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
//根據(jù)order排序
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<Object>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
⑨、執(zhí)行所有SpringApplicationRunListener的finished()方法遵堵。
public void finished(ConfigurableApplicationContext context, Throwable exception) {
for (SpringApplicationRunListener listener : this.listeners) {
callFinishedListener(listener, context, exception);
}
}
private void callFinishedListener(SpringApplicationRunListener listener,
ConfigurableApplicationContext context, Throwable exception) {
try {
listener.finished(context, exception);
}
catch (Throwable ex) {
if (exception == null) {
ReflectionUtils.rethrowRuntimeException(ex);
}
if (this.log.isDebugEnabled()) {
this.log.error("Error handling failed", ex);
}
else {
String message = ex.getMessage();
message = (message == null ? "no error message" : message);
this.log.warn("Error handling failed (" + message + ")");
}
}
}
這就是Spring Boot的整個(gè)啟動(dòng)流程箱玷,其核心就是<u>在Spring容器初始化并啟動(dòng)的基礎(chǔ)上加入各種擴(kuò)展點(diǎn)</u>,這些擴(kuò)展點(diǎn)包括:
- ApplicationContextInitializer
- ApplicationListener
- 各種BeanFactoryPostProcessor
只要理解這些擴(kuò)展點(diǎn)是在何時(shí)如何工作的陌宿,能讓它們?yōu)槟闼眉纯伞?/p>
言而總之锡足,Spring才是核心,理解清楚Spring容器的啟動(dòng)流程限番,那Spring Boot啟動(dòng)流程就不在話下了舱污。