精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

給你一份超詳細(xì)Spring Boot知識(shí)清單

開發(fā) 架構(gòu)
在過去兩三年的 Spring 生態(tài)圈,最讓人興奮的莫過于 Spring Boot 框架。或許從命名上就能看出這個(gè)框架的設(shè)計(jì)初衷:快速的啟動(dòng) Spring 應(yīng)用。

因而 Spring Boot 應(yīng)用本質(zhì)上就是一個(gè)基于 Spring 框架的應(yīng)用,它是 Spring 對(duì)“約定優(yōu)先于配置”理念的***實(shí)踐產(chǎn)物,它能夠幫助開發(fā)者更快速高效地構(gòu)建基于 Spring 生態(tài)圈的應(yīng)用。

那 Spring Boot 有何魔法?自動(dòng)配置、起步依賴、Actuator、命令行界面(CLI) 是 Spring Boot 最重要的 4 大核心特性。

其中 CLI 是 Spring Boot 的可選特性,雖然它功能強(qiáng)大,但也引入了一套不太常規(guī)的開發(fā)模型,因而這個(gè)系列的文章僅關(guān)注其他 3 種特性。

本文將為你打開 Spring Boot 的大門,重點(diǎn)為你剖析其啟動(dòng)流程以及自動(dòng)配置實(shí)現(xiàn)原理。要掌握這部分核心內(nèi)容,理解一些 Spring 框架的基礎(chǔ)知識(shí),將會(huì)讓你事半功倍。

拋磚引玉:探索 Spring IOC 容器

如果有看過 SpringApplication.run() 方法的源碼,Spring Boot 冗長(zhǎng)無比的啟動(dòng)流程一定會(huì)讓你抓狂。

透過現(xiàn)象看本質(zhì),SpringApplication 只是將一個(gè)典型的Spring應(yīng)用的啟動(dòng)流程進(jìn)行了擴(kuò)展,因此,透徹理解 Spring 容器是打開 Spring Boot 大門的一把鑰匙。

Spring IOC 容器

可以把 Spring IOC 容器比作一間餐館,當(dāng)你來到餐館,通常會(huì)直接招呼服務(wù)員:點(diǎn)菜!至于菜的原料是什么?如何用原料把菜做出來?可能你根本就不關(guān)心。

IOC 容器也是一樣,你只需要告訴它需要某個(gè) bean,它就把對(duì)應(yīng)的實(shí)例(instance)扔給你,至于這個(gè) bean 是否依賴其他組件,怎樣完成它的初始化,根本就不需要你關(guān)心。

作為餐館,想要做出菜肴,得知道菜的原料和菜譜,同樣地,IOC 容器想要管理各個(gè)業(yè)務(wù)對(duì)象以及它們之間的依賴關(guān)系,需要通過某種途徑來記錄和管理這些信息。

BeanDefinition 對(duì)象就承擔(dān)了這個(gè)責(zé)任:容器中的每一個(gè) bean 都會(huì)有一個(gè)對(duì)應(yīng)的 BeanDefinition 實(shí)例。

該實(shí)例負(fù)責(zé)保存 bean 對(duì)象的所有必要信息,包括 bean 對(duì)象的 class 類型、是否是抽象類、構(gòu)造方法和參數(shù)、其他屬性等等。

當(dāng)客戶端向容器請(qǐng)求相應(yīng)對(duì)象時(shí),容器就會(huì)通過這些信息為客戶端返回一個(gè)完整可用的 bean 實(shí)例。

原材料已經(jīng)準(zhǔn)備好(把 BeanDefinition 看做原料),開始做菜吧,等等,你還需要一份菜譜。

BeanDefinitionRegistry 和 BeanFactory 就是這份菜譜,BeanDefinitionRegistry 抽象出 bean 的注冊(cè)邏輯。

而 BeanFactory 則抽象出了 bean 的管理邏輯,而各個(gè) BeanFactory 的實(shí)現(xiàn)類就具體承擔(dān)了 bean 的注冊(cè)以及管理工作。

它們之間的關(guān)系就如下圖:

BeanFactory、BeanDefinitionRegistry 關(guān)系圖(來自:Spring 揭秘)

DefaultListableBeanFactory 作為一個(gè)比較通用的 BeanFactory 實(shí)現(xiàn),它同時(shí)也實(shí)現(xiàn)了 BeanDefinitionRegistry 接口,因此它就承擔(dān)了 bean 的注冊(cè)管理工作。

從圖中也可以看出,BeanFactory 接口中主要包含 getBean、containBean、getType、getAliases 等管理 bean 的方法。

而 BeanDefinitionRegistry 接口則包含 registerBeanDefinition、removeBeanDefinition、getBeanDefinition 等注冊(cè)管理 BeanDefinition 的方法。

下面通過一段簡(jiǎn)單的代碼來模擬 BeanFactory 底層是如何工作的:

這段代碼僅為了說明 BeanFactory 底層的大致工作流程,實(shí)際情況會(huì)更加復(fù)雜。

比如 bean 之間的依賴關(guān)系可能定義在外部配置文件(XML/Properties)中、也可能是注解方式。

Spring IoC 容器的整個(gè)工作流程大致可以分為兩個(gè)階段:

①容器啟動(dòng)階段

容器啟動(dòng)時(shí),會(huì)通過某種途徑加載 ConfigurationMetaData。除了代碼方式比較直接外,在大部分情況下,容器需要依賴某些工具類。

比如:BeanDefinitionReader,它會(huì)對(duì)加載的 ConfigurationMetaData 進(jìn)行解析和分析,并將分析后的信息組裝為相應(yīng)的 BeanDefinition。

***把這些保存了 bean 定義的 BeanDefinition,注冊(cè)到相應(yīng)的 BeanDefinitionRegistry,這樣容器的啟動(dòng)工作就完成了。

這個(gè)階段主要完成一些準(zhǔn)備性工作,更側(cè)重于 bean 對(duì)象管理信息的收集,當(dāng)然一些驗(yàn)證性或者輔助性的工作也在這一階段完成。

來看一個(gè)簡(jiǎn)單的例子吧,過往,所有的 bean 都定義在 XML 配置文件中,下面的代碼將模擬 BeanFactory 如何從配置文件中加載 bean 的定義以及依賴關(guān)系:

②Bean 的實(shí)例化階段

經(jīng)過***階段,所有 bean 定義都通過 BeanDefinition 的方式注冊(cè)到 BeanDefinitionRegistry 中。

當(dāng)某個(gè)請(qǐng)求通過容器的 getBean 方法請(qǐng)求某個(gè)對(duì)象,或者因?yàn)橐蕾囮P(guān)系容器需要隱式的調(diào)用 getBean 時(shí),就會(huì)觸發(fā)第二階段的活動(dòng):容器會(huì)首先檢查所請(qǐng)求的對(duì)象之前是否已經(jīng)實(shí)例化完成。

如果沒有,則會(huì)根據(jù)注冊(cè)的 BeanDefinition 所提供的信息實(shí)例化被請(qǐng)求對(duì)象,并為其注入依賴。當(dāng)該對(duì)象裝配完畢后,容器會(huì)立即將其返回給請(qǐng)求方使用。

BeanFactory 只是 Spring IoC 容器的一種實(shí)現(xiàn),如果沒有特殊指定,它采用延遲初始化策略:只有當(dāng)訪問容器中的某個(gè)對(duì)象時(shí),才對(duì)該對(duì)象進(jìn)行初始化和依賴注入操作。

而在實(shí)際場(chǎng)景下,我們更多的使用另外一種類型的容器: ApplicationContext,它構(gòu)建在 BeanFactory 之上,屬于更高級(jí)的容器。

除了具有 BeanFactory 的所有能力之外,還提供對(duì)事件監(jiān)聽機(jī)制以及國(guó)際化的支持等。它管理的 bean,在容器啟動(dòng)時(shí)全部完成初始化和依賴注入操作。

Spring 容器擴(kuò)展機(jī)制

IoC 容器負(fù)責(zé)管理容器中所有 bean 的生命周期,而在 bean 生命周期的不同階段,Spring 提供了不同的擴(kuò)展點(diǎn)來改變 bean 的命運(yùn)。

在容器的啟動(dòng)階段, BeanFactoryPostProcessor 允許我們?cè)谌萜鲗?shí)例化相應(yīng)對(duì)象之前,對(duì)注冊(cè)到容器的 BeanDefinition 所保存的信息做一些額外的操作,比如修改bean定義的某些屬性或者增加其他信息等。

如果要自定義擴(kuò)展類,通常需要實(shí)現(xiàn) org.springframework.beans.factory.config.BeanFactoryPostProcessor 接口。

與此同時(shí),因?yàn)槿萜髦锌赡苡卸鄠€(gè) BeanFactoryPostProcessor,可能還需要實(shí)現(xiàn) org.springframework.core.Ordered 接口,以保證 BeanFactoryPostProcessor 按照順序執(zhí)行。

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 中保存的對(duì)象的屬性還是以占位符方式存在的,比如 ${jdbc.mysql.url}。

當(dāng) PropertyPlaceholderConfigurer 作為 BeanFactoryPostProcessor 被應(yīng)用時(shí),它會(huì)使用 properties 配置文件中的值來替換相應(yīng)的 BeanDefinition 中占位符所表示的屬性值。

當(dāng)需要實(shí)例化 bean 時(shí),bean 定義中的屬性值就已經(jīng)被替換成我們配置的值。

當(dāng)然其實(shí)現(xiàn)比上面描述的要復(fù)雜一些,這里僅說明其大致工作原理,更詳細(xì)的實(shí)現(xiàn)可以參考其源碼。

與之相似的,還有 BeanPostProcessor,其存在于對(duì)象實(shí)例化階段。跟 BeanFactoryPostProcessor 類似,它會(huì)處理容器內(nèi)所有符合條件并且已經(jīng)實(shí)例化后的對(duì)象。

簡(jiǎn)單的對(duì)比,BeanFactoryPostProcessor 處理 bean 的定義,而 BeanPostProcessor 則處理 bean 完成實(shí)例化后的對(duì)象。

BeanPostProcessor 定義了兩個(gè)接口:

 

為了理解這兩個(gè)方法執(zhí)行的時(shí)機(jī),簡(jiǎn)單的了解下 bean 的整個(gè)生命周期:

Bean 的實(shí)例化過程(來自:Spring 揭秘)

postProcessBeforeInitialization()方法與 postProcessAfterInitialization() 分別對(duì)應(yīng)圖中前置處理和后置處理兩個(gè)步驟將執(zhí)行的方法。

這兩個(gè)方法中都傳入了 bean 對(duì)象實(shí)例的引用,為擴(kuò)展容器的對(duì)象實(shí)例化過程提供了很大便利,在這兒幾乎可以對(duì)傳入的實(shí)例執(zhí)行任何操作。

注解、AOP 等功能的實(shí)現(xiàn)均大量使用了 BeanPostProcessor,比如有一個(gè)自定義注解,你完全可以實(shí)現(xiàn) BeanPostProcessor 的接口,在其中判斷 bean 對(duì)象的腦袋上是否有該注解。

如果有,你可以對(duì)這個(gè) bean 實(shí)例執(zhí)行任何操作,想想是不是非常的簡(jiǎn)單?

再來看一個(gè)更常見的例子,在 Spring 中經(jīng)常能夠看到各種各樣的 Aware 接口,其作用就是在對(duì)象實(shí)例化完成以后將 Aware 接口定義中規(guī)定的依賴注入到當(dāng)前實(shí)例中。

比如最常見的 ApplicationContextAware 接口,實(shí)現(xiàn)了這個(gè)接口的類都可以獲取到一個(gè) ApplicationContext 對(duì)象。

當(dāng)容器中每個(gè)對(duì)象的實(shí)例化過程走到 BeanPostProcessor 前置處理這一步時(shí),容器會(huì)檢測(cè)到之前注冊(cè)到容器的 ApplicationContextAwareProcessor。

然后就會(huì)調(diào)用其 postProcessBeforeInitialization() 方法,檢查并設(shè)置 Aware 相關(guān)依賴。

看看代碼吧,是不是很簡(jiǎn)單:

 

***總結(jié)一下,本小節(jié)內(nèi)容和你一起回顧了 Spring 容器的部分核心內(nèi)容,限于篇幅不能寫更多,但理解這部分內(nèi)容,足以讓您輕松理解 Spring Boot 的啟動(dòng)原理。

如果在后續(xù)的學(xué)習(xí)過程中遇到一些晦澀難懂的知識(shí),再回過頭來看看 Spring 的核心知識(shí),也許有意想不到的效果。

也許 Spring Boot 的中文資料很少,但 Spring 的中文資料和書籍有太多太多,總有東西能給你啟發(fā)。

夯實(shí)基礎(chǔ):JavaConfig 與常見 Annotation

JavaConfig

我們知道 bean 是 Spring IOC 中非常核心的概念,Spring 容器負(fù)責(zé) bean 的生命周期的管理。

在最初,Spring 使用 XML 配置文件的方式來描述 bean 的定義以及相互間的依賴關(guān)系,但隨著 Spring 的發(fā)展,越來越多的人對(duì)這種方式表示不滿。

因?yàn)?Spring 項(xiàng)目的所有業(yè)務(wù)類均以 bean 的形式配置在 XML 文件中,造成了大量的 XML 文件,使項(xiàng)目變得復(fù)雜且難以管理。

后來,基于純 Java Annotation 依賴注入框架 Guice 出世,其性能明顯優(yōu)于采用 XML 方式的 Spring。

甚至有部分人認(rèn)為, Guice 可以完全取代 Spring( Guice 僅是一個(gè)輕量級(jí) IOC 框架,取代 Spring 還差的挺遠(yuǎn))。

正是這樣的危機(jī)感,促使 Spring 及社區(qū)推出并持續(xù)完善了 JavaConfig 子項(xiàng)目,它基于 Java 代碼和 Annotation 注解來描述 bean 之間的依賴綁定關(guān)系。

比如,下面是使用 XML 配置方式來描述 bean 的定義:

而基于 JavaConfig 的配置形式是這樣的:

如果兩個(gè) bean 之間有依賴關(guān)系的話,在 XML 配置中應(yīng)該是這樣:

而在 JavaConfig 中則是這樣:

你可能注意到這個(gè)示例中,有兩個(gè) bean 都依賴于 dependencyService,也就是說當(dāng)初始化 bookService 時(shí)會(huì)調(diào)用 dependencyService(),在初始化 otherService 時(shí)也會(huì)調(diào)用 dependencyService(),那么問題來了?

這時(shí)候 IOC 容器中是有一個(gè) dependencyService 實(shí)例還是兩個(gè)?這個(gè)問題留著大家思考吧,這里不再贅述。

@ComponentScan

@ComponentScan 注解對(duì)應(yīng)XML配置形式中的 元素,表示啟用組件掃描,Spring 會(huì)自動(dòng)掃描所有通過注解配置的 bean,然后將其注冊(cè)到 IOC 容器中。

我們可以通過 basePackages 等屬性來指定 @ComponentScan 自動(dòng)掃描的范圍,如果不指定,默認(rèn)從聲明 @ComponentScan 所在類的 package 進(jìn)行掃描。正因?yàn)槿绱耍琒pringBoot 的啟動(dòng)類都默認(rèn)在 src/main/java 下。

@Import

@Import 注解用于導(dǎo)入配置類,舉個(gè)簡(jiǎn)單的例子:

 

現(xiàn)在有另外一個(gè)配置類,比如:MoonUserConfiguration,這個(gè)配置類中有一個(gè) bean 依賴于 MoonBookConfiguration 中的 bookService,如何將這兩個(gè) bean 組合在一起?

借助 @Import 即可:

 

需要注意的是,在 4.2 之前, @Import 注解只支持導(dǎo)入配置類,但是在 4.2 之后,它支持導(dǎo)入普通類,并將這個(gè)類作為一個(gè) bean 的定義注冊(cè)到 IOC 容器中。

@Conditional

@Conditional 注解表示在滿足某種條件后才初始化一個(gè) bean 或者啟用某些配置。

它一般用在由 @Component、@Service、@Configuration 等注解標(biāo)識(shí)的類上面,或者由 @Bean 標(biāo)記的方法上。

如果一個(gè) @Configuration 類標(biāo)記了 @Conditional,則該類中所有標(biāo)識(shí)了 @Bean 的方法和 @Import 注解導(dǎo)入的相關(guān)類將遵從這些條件。

在 Spring 里可以很方便的編寫你自己的條件類,所要做的就是實(shí)現(xiàn) Condition 接口,并覆蓋它的 matches()方法。

舉個(gè)例子,下面的簡(jiǎn)單條件類表示只有在 Classpath 里存在 JdbcTemplate 類時(shí)才生效:

 

當(dāng)你用 Java 來聲明 bean 的時(shí)候,可以使用這個(gè)自定義條件類:

 

這個(gè)例子中只有當(dāng) JdbcTemplateCondition 類的條件成立時(shí)才會(huì)創(chuàng)建 MyService 這個(gè) bean。

也就是說 MyService 這 bean 的創(chuàng)建條件是 classpath 里面包含 JdbcTemplate,否則這個(gè) bean 的聲明就會(huì)被忽略掉。

Spring Boot 定義了很多有趣的條件,并把他們運(yùn)用到了配置類上,這些配置類構(gòu)成了 Spring Boot 的自動(dòng)配置的基礎(chǔ)。

Spring Boot 運(yùn)用條件化配置的方法是:定義多個(gè)特殊的條件化注解,并將它們用到配置類上。

下面列出了 Spring Boot 提供的部分條件化注解:

@ConfigurationProperties與@EnableConfigurationProperties

當(dāng)某些屬性的值需要配置的時(shí)候,我們一般會(huì)在 application.properties 文件中新建配置項(xiàng),然后在 bean 中使用 @Value 注解來獲取配置的值。

比如下面配置數(shù)據(jù)源的代碼:

 

使用 @Value 注解注入的屬性通常都比較簡(jiǎn)單,如果同一個(gè)配置在多個(gè)地方使用,也存在不方便維護(hù)的問題(考慮下,如果有幾十個(gè)地方在使用某個(gè)配置,而現(xiàn)在你想改下名字,你該怎么做?)。

對(duì)于更為復(fù)雜的配置,Spring Boot 提供了更優(yōu)雅的實(shí)現(xiàn)方式,那就是 @ConfigurationProperties 注解。

我們可以通過下面的方式來改寫上面的代碼:

 

@ConfigurationProperties 對(duì)于更為復(fù)雜的配置,處理起來也是得心應(yīng)手,比如有如下配置文件:

 

可以定義如下配置類來接收這些屬性:

 

@EnableConfigurationProperties 注解表示對(duì) @ConfigurationProperties 的內(nèi)嵌支持,默認(rèn)會(huì)將對(duì)應(yīng) Properties Class 作為 bean 注入的 IOC 容器中,即在相應(yīng)的 Properties 類上不用加 @Component 注解。

削鐵如泥:SpringFactoriesLoader 詳解

JVM 提供了 3 種類加載器: BootstrapClassLoader、 ExtClassLoader、 AppClassLoader 分別加載 Java 核心類庫(kù)、擴(kuò)展類庫(kù)以及應(yīng)用的類路徑( CLASSPATH)下的類庫(kù)。

JVM 通過雙親委派模型進(jìn)行類的加載,我們也可以通過繼承 java.lang.classloader 實(shí)現(xiàn)自己的類加載器。

何為雙親委派模型?當(dāng)一個(gè)類加載器收到類加載任務(wù)時(shí),會(huì)先交給自己的父加載器去完成。

因此最終加載任務(wù)都會(huì)傳遞到最頂層的 BootstrapClassLoader,只有當(dāng)父加載器無法完成加載任務(wù)時(shí),才會(huì)嘗試自己來加載。

采用雙親委派模型的一個(gè)好處是保證使用不同類加載器最終得到的都是同一個(gè)對(duì)象,這樣就可以保證 Java 核心庫(kù)的類型安全。

比如,加載位于 rt.jar 包中的 java.lang.Object 類,不管是哪個(gè)加載器加載這個(gè)類,最終都是委托給頂層的 BootstrapClassLoader 來加載的,這樣就可以保證任何的類加載器最終得到的都是同樣一個(gè) Object 對(duì)象。

查看 ClassLoader 的源碼,對(duì)雙親委派模型會(huì)有更直觀的認(rèn)識(shí):

 

但雙親委派模型并不能解決所有的類加載器問題,比如,Java 提供了很多服務(wù)提供者接口( ServiceProviderInterface,SPI),允許第三方為這些接口提供實(shí)現(xiàn)。

常見的 SPI 有 JDBC、JNDI、JAXP 等,這些 SPI 的接口由核心類庫(kù)提供,卻由第三方實(shí)現(xiàn)。

這樣就存在一個(gè)問題:SPI 的接口是 Java 核心庫(kù)的一部分,是由 BootstrapClassLoader 加載的;SPI 實(shí)現(xiàn)的 Java 類一般是由 AppClassLoader 來加載的。

BootstrapClassLoader 是無法找到 SPI 的實(shí)現(xiàn)類的,因?yàn)樗患虞d Java 的核心庫(kù)。

它也不能代理給 AppClassLoader,因?yàn)樗亲铐攲拥念惣虞d器。也就是說,雙親委派模型并不能解決這個(gè)問題。

線程上下文類加載器( ContextClassLoader)正好解決了這個(gè)問題。從名稱上看,可能會(huì)誤解為它是一種新的類加載器,實(shí)際上,它僅僅是 Thread 類的一個(gè)變量而已。

可以通過 setContextClassLoader(ClassLoadercl)和 getContextClassLoader()來設(shè)置和獲取該對(duì)象。

如果不做任何的設(shè)置,Java 應(yīng)用的線程的上下文類加載器默認(rèn)就是 AppClassLoader。

在核心類庫(kù)使用 SPI 接口時(shí),傳遞的類加載器使用線程上下文類加載器,就可以成功的加載到 SPI 實(shí)現(xiàn)的類。

線程上下文類加載器在很多 SPI 的實(shí)現(xiàn)中都會(huì)用到。但在 JDBC 中,你可能會(huì)看到一種更直接的實(shí)現(xiàn)方式。

比如,JDBC 驅(qū)動(dòng)管理 java.sql.Driver 中的 loadInitialDrivers() 方法中,你可以直接看到 JDK 是如何加載驅(qū)動(dòng)的:

 

其實(shí)講解線程上下文類加載器,最主要是讓大家在看到 Thread.currentThread().getClassLoader()和Thread.currentThread().getContextClassLoader()時(shí)不會(huì)一臉懵逼。

這兩者除了在許多底層框架中取得的 ClassLoader 可能會(huì)有所不同外,其他大多數(shù)業(yè)務(wù)場(chǎng)景下都是一樣的,大家只要知道它是為了解決什么問題而存在的即可。

類加載器除了加載 class 外,還有一個(gè)非常重要功能,就是加載資源,它可以從 jar 包中讀取任何資源文件。

比如, ClassLoader.getResources(Stringname) 方法就是用于讀取 jar 包中的資源文件,其代碼如下:

 

是不是覺得有點(diǎn)眼熟,沒錯(cuò),它的邏輯其實(shí)跟類加載的邏輯是一樣的,首先判斷父類加載器是否為空,不為空則委托父類加載器執(zhí)行資源查找任務(wù),直到 BootstrapClassLoader,***才輪到自己查找。

而不同的類加載器負(fù)責(zé)掃描不同路徑下的 jar 包,就如同加載 class 一樣,***會(huì)掃描所有的 jar 包,找到符合條件的資源文件。

類加載器的 findResources(name) 方法會(huì)遍歷其負(fù)責(zé)加載的所有 jar 包,找到 jar 包中名稱為 name 的資源文件,這里的資源可以是任何文件,甚至是 .class 文件。

比如下面的示例,用于查找 Array.class 文件:

 

運(yùn)行后可以得到如下結(jié)果:

 

根據(jù)資源文件的 URL,可以構(gòu)造相應(yīng)的文件來讀取資源內(nèi)容。

看到這里,你可能會(huì)感到挺奇怪的,你不是要詳解 SpringFactoriesLoader 嗎?上來講了一堆 ClassLoader 是幾個(gè)意思?

看下它的源碼你就知道了:

 

有了前面關(guān)于 ClassLoader 的知識(shí),再來理解這段代碼,是不是感覺豁然開朗:從 CLASSPATH 下的每個(gè) jar 包中搜尋所有 META-INF/spring.factories 配置文件,然后將解析 properties 文件,找到指定名稱的配置后返回。

需要注意的是,其實(shí)這里不僅僅是會(huì)去 ClassPath 路徑下查找,會(huì)掃描所有路徑下的 jar 包,只不過這個(gè)文件只會(huì)在 ClassPath 下的 jar 包中。

來簡(jiǎn)單看下 spring.factories 文件的內(nèi)容吧:

 

執(zhí)行 loadFactoryNames(EnableAutoConfiguration.class,classLoader)后,得到對(duì)應(yīng)的一組 @Configuration 類。

我們就可以通過反射實(shí)例化這些類然后注入到 IOC 容器中,***容器里就有了一系列標(biāo)注了 @Configuration 的 JavaConfig 形式的配置類。

這就是 SpringFactoriesLoader,它本質(zhì)上屬于 Spring 框架私有的一種擴(kuò)展方案,類似于 SPI,Spring Boot 在 Spring 基礎(chǔ)上的很多核心功能都是基于此,希望大家可以理解。

另一件武器:Spring 容器的事件監(jiān)聽機(jī)制

過去,事件監(jiān)聽機(jī)制多用于圖形界面編程,比如:點(diǎn)擊按鈕、在文本框輸入內(nèi)容等操作被稱為事件。

而當(dāng)事件觸發(fā)時(shí),應(yīng)用程序作出一定的響應(yīng)則表示應(yīng)用監(jiān)聽了這個(gè)事件,而在服務(wù)器端,事件的監(jiān)聽機(jī)制更多的用于異步通知以及監(jiān)控和異常處理。

Java 提供了實(shí)現(xiàn)事件監(jiān)聽機(jī)制的兩個(gè)基礎(chǔ)類:自定義事件類型擴(kuò)展自 java.util.EventObject、事件的監(jiān)聽器擴(kuò)展自 java.util.EventListener。

來看一個(gè)簡(jiǎn)單的實(shí)例:簡(jiǎn)單的監(jiān)控一個(gè)方法的耗時(shí)。

首先定義事件類型,通常的做法是擴(kuò)展 EventObject,隨著事件的發(fā)生,相應(yīng)的狀態(tài)通常都封裝在此類中:

 

事件發(fā)布之后,相應(yīng)的監(jiān)聽器即可對(duì)該類型的事件進(jìn)行處理,我們可以在方法開始執(zhí)行之前發(fā)布一個(gè) begin 事件。

在方法執(zhí)行結(jié)束之后發(fā)布一個(gè) end 事件,相應(yīng)地,事件監(jiān)聽器需要提供方法對(duì)這兩種情況下接收到的事件進(jìn)行處理:

 

事件監(jiān)聽器接口針對(duì)不同的事件發(fā)布實(shí)際提供相應(yīng)的處理方法定義,最重要的是,其方法只接收 MethodMonitorEvent 參數(shù),說明這個(gè)監(jiān)聽器類只負(fù)責(zé)監(jiān)聽器對(duì)應(yīng)的事件并進(jìn)行處理。

有了事件和監(jiān)聽器,剩下的就是發(fā)布事件,然后讓相應(yīng)的監(jiān)聽器監(jiān)聽并處理。

通常情況,我們會(huì)有一個(gè)事件發(fā)布者,它本身作為事件源,在合適的時(shí)機(jī),將相應(yīng)的事件發(fā)布給對(duì)應(yīng)的事件監(jiān)聽器:

對(duì)于事件發(fā)布者(事件源)通常需要關(guān)注兩點(diǎn):

  • 在合適的時(shí)機(jī)發(fā)布事件。此例中的 methodMonitor() 方法是事件發(fā)布的源頭,其在方法執(zhí)行之前和結(jié)束之后兩個(gè)時(shí)間點(diǎn)發(fā)布 MethodMonitorEvent 事件,每個(gè)時(shí)間點(diǎn)發(fā)布的事件都會(huì)傳給相應(yīng)的監(jiān)聽器進(jìn)行處理。

在具體實(shí)現(xiàn)時(shí)需要注意的是,事件發(fā)布是順序執(zhí)行,為了不影響處理性能,事件監(jiān)聽器的處理邏輯應(yīng)盡量簡(jiǎn)單。

  • 事件監(jiān)聽器的管理。publisher 類中提供了事件監(jiān)聽器的注冊(cè)與移除方法,這樣客戶端可以根據(jù)實(shí)際情況決定是否需要注冊(cè)新的監(jiān)聽器或者移除某個(gè)監(jiān)聽器。

如果這里沒有提供 remove 方法,那么注冊(cè)的監(jiān)聽器示例將一直被 MethodMonitorEventPublisher 引用,即使已經(jīng)廢棄不用了,也依然在發(fā)布者的監(jiān)聽器列表中,這會(huì)導(dǎo)致隱性的內(nèi)存泄漏。

Spring 容器內(nèi)的事件監(jiān)聽機(jī)制

Spring 的 ApplicationContext 容器內(nèi)部中的所有事件類型均繼承自 org.springframework.context.AppliationEvent。

容器中的所有監(jiān)聽器都實(shí)現(xiàn) org.springframework.context.ApplicationListener 接口,并且以 bean 的形式注冊(cè)在容器中。

一旦在容器內(nèi)發(fā)布 ApplicationEvent 及其子類型的事件,注冊(cè)到容器的 ApplicationListener 就會(huì)對(duì)這些事件進(jìn)行處理。

你應(yīng)該已經(jīng)猜到是怎么回事了。

ApplicationEvent 繼承自 EventObject,Spring 提供了一些默認(rèn)的實(shí)現(xiàn),比如:ContextClosedEvent 表示容器在即將關(guān)閉時(shí)發(fā)布的事件類型, ContextRefreshedEvent 表示容器在初始化或者刷新的時(shí)候發(fā)布的事件類型......

容器內(nèi)部使用 ApplicationListener 作為事件監(jiān)聽器接口定義,它繼承自 EventListener。

ApplicationContext 容器在啟動(dòng)時(shí),會(huì)自動(dòng)識(shí)別并加載 EventListener 類型的bean,一旦容器內(nèi)有事件發(fā)布,將通知這些注冊(cè)到容器的 EventListener。

ApplicationContext 接口繼承了 ApplicationEventPublisher 接口,該接口提供了 voidpublishEvent(ApplicationEventevent) 方法定義,不難看出,ApplicationContext 容器擔(dān)當(dāng)?shù)木褪鞘录l(fā)布者的角色。

如果有興趣可以查看 AbstractApplicationContext.publishEvent(ApplicationEventevent) 方法的源碼:ApplicationContext 將事件的發(fā)布以及監(jiān)聽器的管理工作委托給 ApplicationEventMulticaster 接口的實(shí)現(xiàn)類。

在容器啟動(dòng)時(shí),會(huì)檢查容器內(nèi)是否存在名為 applicationEventMulticaster 的 ApplicationEventMulticaster 對(duì)象實(shí)例。

如果有就使用其提供的實(shí)現(xiàn),沒有就默認(rèn)初始化一個(gè) SimpleApplicationEventMulticaster 作為實(shí)現(xiàn)。

***,如果我們業(yè)務(wù)需要在容器內(nèi)部發(fā)布事件,只需要為其注入 ApplicationEventPublisher 依賴即可實(shí)現(xiàn) ApplicationEventPublisherAware 接口或者 ApplicationContextAware 接口(Aware 接口相關(guān)內(nèi)容請(qǐng)回顧上文)。

出神入化:揭秘自動(dòng)配置原理

典型的 Spring Boot 應(yīng)用的啟動(dòng)類一般均位于 src/main/java 根路徑下,比如 MoonApplication 類:

 

其中 @SpringBootApplication 開啟組件掃描和自動(dòng)配置,而 SpringApplication.run 則負(fù)責(zé)啟動(dòng)引導(dǎo)應(yīng)用程序。

@SpringBootApplication 是一個(gè)復(fù)合 Annotation,它將三個(gè)有用的注解組合在一起:

@SpringBootConfiguration 就是 @Configuration,它是 Spring 框架的注解,標(biāo)明該類是一個(gè) JavaConfig 配置類。

而 @ComponentScan 啟用組件掃描,前文已經(jīng)詳細(xì)講解過,這里著重關(guān)注 @EnableAutoConfiguration。

@EnableAutoConfiguration 注解表示開啟 Spring Boot 自動(dòng)配置功能,Spring Boot 會(huì)根據(jù)應(yīng)用的依賴、自定義的 bean、Classpath 下有沒有某個(gè)類等等因素來猜測(cè)你需要的 bean,然后注冊(cè)到 IOC 容器中。

那 @EnableAutoConfiguration 是如何推算出你的需求?首先看下它的定義:

你的關(guān)注點(diǎn)應(yīng)該在 @Import(EnableAutoConfigurationImportSelector.class)上了。

前文說過,@Import 注解用于導(dǎo)入類,并將這個(gè)類作為一個(gè) bean 的定義注冊(cè)到容器中,這里它將把 EnableAutoConfigurationImportSelector 作為 bean 注入到容器中。

而這個(gè)類會(huì)將所有符合條件的 @Configuration 配置都加載到容器中,看看它的代碼:

這個(gè)類會(huì)掃描所有的 Jar 包,將所有符合條件的 @Configuration 配置類注入到容器中,何為符合條件,看看 META-INF/spring.factories 的文件內(nèi)容:

以 DataSourceAutoConfiguration 為例,看看 Spring Boot 是如何自動(dòng)配置的:

分別說一說:

  • @ConditionalOnClass({DataSource.class,EmbeddedDatabaseType.class}):當(dāng) Classpath 中存在 DataSource 或者 EmbeddedDatabaseType 類時(shí)才啟用這個(gè)配置,否則這個(gè)配置將被忽略。
  • @EnableConfigurationProperties(DataSourceProperties.class):將 DataSource 的默認(rèn)配置類注入到 IOC 容器中,DataSourceproperties 定義為:

  • @Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class }):導(dǎo)入其他額外的配置,就以 DataSourcePoolMetadataProvidersConfiguration 為例吧:

DataSourcePoolMetadataProvidersConfiguration 是數(shù)據(jù)庫(kù)連接池提供者的一個(gè)配置類。

即 Classpath 中存在 org.apache.tomcat.jdbc.pool.DataSource.class,則使用 tomcat-jdbc 連接池,如果 Classpath 中存在 HikariDataSource.class 則使用 Hikari 連接池。

這里僅描述了 DataSourceAutoConfiguration 的冰山一角,但足以說明 Spring Boot 如何利用條件化配置來實(shí)現(xiàn)自動(dòng)配置的。

回顧一下,@EnableAutoConfiguration 中導(dǎo)入了 EnableAutoConfigurationImportSelector 類。

而這個(gè)類的 selectImports() 通過 SpringFactoriesLoader 得到了大量的配置類,而每一個(gè)配置類則根據(jù)條件化配置來做出決策,以實(shí)現(xiàn)自動(dòng)配置。

整個(gè)流程很清晰,但漏了一個(gè)大問題: EnableAutoConfigurationImportSelector.selectImports() 是何時(shí)執(zhí)行的?

其實(shí)這個(gè)方法會(huì)在容器啟動(dòng)過程中執(zhí)行: AbstractApplicationContext.refresh(),更多的細(xì)節(jié)在下一小節(jié)中說明。

啟動(dòng)引導(dǎo):Spring Boot 應(yīng)用啟動(dòng)的秘密

SpringApplication 初始化

Spring Boot 整個(gè)啟動(dòng)流程分為兩個(gè)步驟:初始化一個(gè) SpringApplication 對(duì)象、執(zhí)行該對(duì)象的 run 方法。

看下 SpringApplication 的初始化流程,SpringApplication 的構(gòu)造方法中調(diào)用 initialize(Object[] sources) 方法,其代碼如下:

初始化流程中最重要的就是通過 SpringFactoriesLoader 找到 spring.factories 文件中配置的 ApplicationContextInitializer 和 ApplicationListener 兩個(gè)接口的實(shí)現(xiàn)類名稱,以便后期構(gòu)造相應(yīng)的實(shí)例。

ApplicationContextInitializer 的主要目的是在 ConfigurableApplicationContext 做 refresh 之前,對(duì) ConfigurableApplicationContext 實(shí)例做進(jìn)一步的設(shè)置或處理。

ConfigurableApplicationContext 繼承自 ApplicationContext,其主要提供了對(duì) ApplicationContext 進(jìn)行設(shè)置的能力。

實(shí)現(xiàn)一個(gè) ApplicationContextInitializer 非常簡(jiǎn)單,因?yàn)樗挥幸粋€(gè)方法,但大多數(shù)情況下我們沒有必要自定義一個(gè) ApplicationContextInitializer。

即便是 Spring Boot 框架,它默認(rèn)也只是注冊(cè)了兩個(gè)實(shí)現(xiàn),畢竟 Spring 的容器已經(jīng)非常成熟和穩(wěn)定,你沒有必要來改變它。

而 ApplicationListener 的目的就沒什么好說的了,它是 Spring 框架對(duì) Java 事件監(jiān)聽機(jī)制的一種框架實(shí)現(xiàn),具體內(nèi)容在前文 Spring 事件監(jiān)聽機(jī)制這個(gè)小節(jié)有詳細(xì)講解。

這里主要說說,如果你想為 Spring Boot 應(yīng)用添加監(jiān)聽器,該如何實(shí)現(xiàn)?

Spring Boot 提供兩種方式來添加自定義監(jiān)聽器:

  • 通過 SpringApplication.addListeners(ApplicationListener...listeners)或者 SpringApplication.setListeners(Collection>listeners)兩個(gè)方法來添加一個(gè)或者多個(gè)自定義監(jiān)聽器。
  • 既然 SpringApplication 的初始化流程中已經(jīng)從 spring.factories 中獲取到 ApplicationListener 的實(shí)現(xiàn)類,那么我們直接在自己的 jar 包的 META-INF/spring.factories 文件中新增配置即可:

關(guān)于 SpringApplication 的初始化,我們就說這么多。

Spring Boot 啟動(dòng)流程

Spring Boot 應(yīng)用的整個(gè)啟動(dòng)流程都封裝在 SpringApplication.run 方法中,其整個(gè)流程真的是太長(zhǎng)太長(zhǎng)了,但本質(zhì)上就是在 Spring 容器啟動(dòng)的基礎(chǔ)上做了大量的擴(kuò)展,按照這個(gè)思路來看看源碼:

①通過 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)對(duì)這些事件感興趣,則可以接收并且處理。

還記得初始化流程中,SpringApplication 加載了一系列 ApplicationListener 嗎?

這個(gè)啟動(dòng)流程中沒有發(fā)現(xiàn)有發(fā)布事件的代碼,其實(shí)都已經(jīng)在 SpringApplicationRunListeners 這兒實(shí)現(xiàn)了。

簡(jiǎn)單的分析一下其實(shí)現(xiàn)流程,首先看下 SpringApplicationRunListener 的源碼:

SpringApplicationRunListener 只有一個(gè)實(shí)現(xiàn)類: EventPublishingRunListener。

①處的代碼只會(huì)獲取到一個(gè) EventPublishingRunListener 的實(shí)例,我們來看看 starting() 方法的內(nèi)容:

順著這個(gè)邏輯,你可以在 ② 處的 prepareEnvironment() 方法的源碼中找到 listeners.environmentPrepared(environment)。

即 SpringApplicationRunListener 接口的第二個(gè)方法,那不出你所料, environmentPrepared()又發(fā)布了另外一個(gè)事件 ApplicationEnvironmentPreparedEvent。接下來會(huì)發(fā)生什么,就不用我多說了吧。

②創(chuàng)建并配置當(dāng)前應(yīng)用將要使用的 Environment。

Environment 用于描述應(yīng)用程序當(dāng)前的運(yùn)行環(huán)境,其抽象了兩個(gè)方面的內(nèi)容:配置文件(profile)和屬性(properties)。

開發(fā)經(jīng)驗(yàn)豐富的同學(xué)對(duì)這兩個(gè)東西一定不會(huì)陌生:不同的環(huán)境(eg:生產(chǎn)環(huán)境、預(yù)發(fā)布環(huán)境)可以使用不同的配置文件,而屬性則可以從配置文件、環(huán)境變量、命令行參數(shù)等來源獲取。

因此,當(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)備好。

③Spring Boot 應(yīng)用在啟動(dòng)時(shí)會(huì)輸出這樣的東西:

如果想把這個(gè)東西改成自己的涂鴉,你可以研究一下 Banner 的實(shí)現(xiàn),這個(gè)任務(wù)就留給你們吧。

④根據(jù)是否是 Web 項(xiàng)目,來創(chuàng)建不同的 ApplicationContext 容器。

⑤創(chuàng)建一系列 FailureAnalyzer。

創(chuàng)建流程依然是通過 SpringFactoriesLoader 獲取到所有實(shí)現(xiàn) FailureAnalyzer 接口的 class,然后再創(chuàng)建對(duì)應(yīng)的實(shí)例。FailureAnalyzer 用于分析故障并提供相關(guān)診斷信息。

⑥初始化 ApplicationContext。

主要完成以下工作:

  • 將準(zhǔn)備好的 Environment 設(shè)置給 ApplicationContext。
  • 遍歷調(diào)用所有的 ApplicationContextInitializer 的 initialize() 方法來對(duì)已經(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)裝載完畢。

⑦調(diào)用 ApplicationContext 的 refresh() 方法,完成 IOC 容器可用的***一道工序。

從名字上理解為刷新容器,那何為刷新?就是插手容器的啟動(dòng),聯(lián)系一下***小節(jié)的內(nèi)容。那如何刷新呢?

且看下面代碼:

看看這個(gè)方法的實(shí)現(xiàn):

獲取到所有的 BeanFactoryPostProcessor 來對(duì)容器做一些額外的操作。BeanFactoryPostProcessor 允許我們?cè)谌萜鲗?shí)例化相應(yīng)對(duì)象之前,對(duì)注冊(cè)到容器的 BeanDefinition 所保存的信息做一些額外的操作。

這里的 getBeanFactoryPostProcessors() 方法可以獲取到 3 個(gè) Processor:

不是有那么多 BeanFactoryPostProcessor 的實(shí)現(xiàn)類,為什么這兒只有這 3 個(gè)?

因?yàn)樵诔跏蓟鞒太@取到的各種 ApplicationContextInitializer 和 ApplicationListener 中,只有上文 3 個(gè)做了類似于如下操作:

然后你就可以進(jìn)入到 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors() 方法了。

這個(gè)方法除了會(huì)遍歷上面的 3 個(gè) BeanFactoryPostProcessor 處理外,還會(huì)獲取類型為 BeanDefinitionRegistryPostProcessor 的 bean: org.springframework.context.annotation.internalConfigurationAnnotationProcessor,對(duì)應(yīng)的 Class 為 ConfigurationClassPostProcessor。

ConfigurationClassPostProcessor 用于解析處理各種注解,包括:

  • @Configuration
  • @ComponentScan
  • @Import
  • @PropertySource
  • @ImportResource
  • @Bean

當(dāng)處理 @import 注解的時(shí)候,就會(huì)調(diào)用<自動(dòng)配置>這一小節(jié)中的 EnableAutoConfigurationImportSelector.selectImports() 來完成自動(dòng)配置功能。其他的這里不再多講,如果你有興趣,可以查閱參考資料 6。

⑧查找當(dāng)前 context 中是否注冊(cè)有 CommandLineRunner 和 ApplicationRunner,如果有則遍歷執(zhí)行它們。

⑨執(zhí)行所有 SpringApplicationRunListener 的 finished() 方法。

這就是 Spring Boot 的整個(gè)啟動(dòng)流程,其核心就是在 Spring 容器初始化并啟動(dòng)的基礎(chǔ)上加入各種擴(kuò)展點(diǎn)。

這些擴(kuò)展點(diǎn)包括:ApplicationContextInitializer、ApplicationListener 以及各種 BeanFactoryPostProcessor 等等。

你對(duì)整個(gè)流程的細(xì)節(jié)不必太過關(guān)注,甚至沒弄明白也沒有關(guān)系,你只要理解這些擴(kuò)展點(diǎn)是在何時(shí)如何工作的,能讓它們?yōu)槟闼眉纯伞?/p>

整個(gè)啟動(dòng)流程確實(shí)非常復(fù)雜,可以查詢參考資料中的部分章節(jié)和內(nèi)容,對(duì)照著源碼,多看看,我想最終你都能弄清楚的。

言而總之,Spring 才是核心,理解清楚 Spring 容器的啟動(dòng)流程,那 Spring Boot 啟動(dòng)流程就不在話下了。

 

責(zé)任編輯:武曉燕 來源: 簡(jiǎn)書
相關(guān)推薦

2019-01-15 09:34:30

MySQL高性能優(yōu)化

2016-08-24 16:55:18

DevOps結(jié)構(gòu)清單

2023-09-26 11:03:42

數(shù)據(jù)中心服務(wù)器

2023-09-03 23:04:36

網(wǎng)絡(luò)互聯(lián)網(wǎng)

2020-06-08 09:15:14

前端 開發(fā) Git

2018-08-15 13:49:06

數(shù)據(jù)分析學(xué)習(xí)Python

2023-03-21 09:44:34

模型AI

2018-05-16 09:00:00

物聯(lián)網(wǎng)物聯(lián)網(wǎng)平臺(tái)IoT

2019-11-14 21:21:50

數(shù)據(jù)挖掘數(shù)據(jù)處理數(shù)據(jù)分析

2023-04-28 15:41:08

模型ChatGPT

2018-03-30 10:10:11

區(qū)塊鏈數(shù)字貨幣記賬模式

2018-08-21 08:49:53

Nginx服務(wù)器配置

2018-05-22 09:07:54

數(shù)據(jù)科學(xué)語言職位

2023-09-27 12:28:08

Kubernetes命令

2025-09-17 08:14:16

2012-04-11 10:37:48

2021-04-25 08:24:10

Linux系統(tǒng)Adobe全家桶Office套件

2024-07-10 12:11:30

數(shù)據(jù)經(jīng)營(yíng)分析業(yè)務(wù)

2021-05-17 11:14:26

前端HTTP服務(wù)器

2025-07-03 09:28:44

架構(gòu)群消息開發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

天天色天天综合网| 亚洲欧美国产不卡| 日韩在线视频不卡| 成人3d精品动漫精品一二三| 51精品秘密在线观看| 日本熟妇人妻xxxx| 国产黄在线播放| 国产精品一区二区在线观看网站| 91精品国产91久久久久久| 国产精品成人一区二区三区电影毛片| 久久精品xxxxx| 亚洲午夜激情网站| 天堂一区二区三区 | 国精产品一区二区三区| 一区二区三区在线视频111| 日韩激情一区二区三区| 色综合综合色| 日韩久久精品一区| www.激情小说.com| sm在线播放| 综合久久综合久久| 欧美极品jizzhd欧美| 国产成人麻豆精品午夜在线| 视频一区视频二区中文字幕| 欧美高清在线视频观看不卡| japanese中文字幕| 久久久久97| 欧美一二三区在线观看| 日韩中文字幕免费在线| a'aaa级片在线观看| 中文字幕色av一区二区三区| 日本成人三级电影网站| 欧美一区,二区| 国产精品香蕉一区二区三区| 国产欧美韩国高清| www.国产一区二区| 国产日韩高清一区二区三区在线| 不卡中文字幕av| 国产又黄又粗又猛又爽的| 日韩激情网站| 精品成人佐山爱一区二区| 国产一级片中文字幕| 成人在线黄色| 欧美日韩国产在线观看网站| 麻豆一区二区在线| 欧美一区在线直播| 黄色小视频在线免费看| 亚洲精品中文字幕乱码| 在线看国产精品| 亚洲一区二区自偷自拍 | 老司机福利av| 国产成人一二片| 欧美一区午夜视频在线观看| 久久久精品高清| 欧美另类激情| 欧美美女一区二区在线观看| 蜜臀视频一区二区三区| 日韩av超清在线观看| 在线亚洲人成电影网站色www| 久久久久人妻精品一区三寸| 天天免费亚洲黑人免费| 色综合天天天天做夜夜夜夜做| 欧美二区在线视频| 天堂av在线网| 色综合天天综合在线视频| 91视频最新入口| 日韩成人av电影| 在线观看网站黄不卡| 看欧美ab黄色大片视频免费| 成人日韩av| 在线播放亚洲一区| 97免费公开视频| 亚洲天堂av资源在线观看| 日韩精品一区二| 插我舔内射18免费视频| 亚洲欧美日本伦理| 中文日韩电影网站| 人妻少妇精品一区二区三区| 欧美精品激情| 69av视频在线播放| 怡红院av久久久久久久| 久久99热狠狠色一区二区| 亚洲一区二区日本| 人妻视频一区二区三区| 久久九九国产精品| 国产一区二区在线看| 日韩暖暖在线视频| 国产一区二区在线视频聊天| 国产高清精品在线| 精品国产日本| 在线免费黄色| 亚洲3atv精品一区二区三区| 国产v亚洲v天堂无码久久久| 精品久久亚洲| 精品在线观看国产| 男人在线观看视频| 国产日韩欧美一区| 成人动漫网站在线观看| 欧日韩在线视频| 中文字幕国产精品一区二区| 高清无码一区二区在线观看吞精| 色戒汤唯在线观看| 555夜色666亚洲国产免| 黄色国产在线观看| 我不卡影院28| 777精品视频| 国产精品久久777777换脸| av午夜一区麻豆| 91制片厂免费观看| 成人亚洲欧美| 欧美大片国产精品| 秋霞网一区二区三区| 伊人成人在线视频| 国产精品自在线| 色哟哟在线观看| 夜夜嗨av一区二区三区四季av| 欧美日韩怡红院| 嗯用力啊快一点好舒服小柔久久| 在线观看日韩www视频免费| 国产真实乱人偷精品视频| 秋霞av亚洲一区二区三| 国模一区二区三区私拍视频| 麻豆免费在线观看| 91福利在线免费观看| 91精品又粗又猛又爽| 国产精品99久久精品| 日韩美女免费观看| 天堂v在线观看| av免费在线观看网址| 国产精品视频九色porn| 欧美日韩精品在线一区二区| 久久久久亚洲精品中文字幕| 中文字幕免费精品一区高清| 国产亚洲欧美在线精品| av中文字幕在线不卡| av在线com| 色妞ww精品视频7777| 久久精品免费电影| 伊人网视频在线| 国产拍揄自揄精品视频麻豆 | 一区二区三区高清视频在线观看| 亚洲综合中文字幕68页| 免费在线午夜视频| 欧美日韩一区久久| 一级黄色录像毛片| 日韩av电影一区| 日韩成人在线资源| 日韩色淫视频| 在线亚洲国产精品网| 波多野结衣一区二区三区在线| 26uuu亚洲综合色| 成人黄色片视频| 国产aⅴ精品一区二区三区久久| 国模精品一区二区三区色天香| 黄色av中文字幕| 亚洲第一主播视频| 日韩精品一区二区三区高清免费| 一区二区三区国产在线| 欧美精品七区| 成人软件在线观看| 在线观看久久av| 亚洲无码精品国产| 中文字幕佐山爱一区二区免费| 最新av免费在线观看| 国产精品久久久久久麻豆一区软件| 91精品啪aⅴ在线观看国产| 成人欧美在线| 精品国产凹凸成av人导航| 日本五十路女优| 久久久欧美精品sm网站| 青青青在线视频免费观看| 欧美综合另类| 91亚色免费| 一区二区高清视频| 成人在线观看亚洲| 精品国偷自产国产一区| 日产欧产va高清| 久久九九影视网| 久久久久久久高清| 伊人成年综合电影网| 鲁丝片一区二区三区| 123成人网| 欧美激情免费观看| 欧洲视频在线免费观看| 欧美日韩欧美一区二区| 欧美成人一二三区| 久久久精品一品道一区| 天天做天天干天天操| 在线播放一区| 色播亚洲视频在线观看| 精品午夜av| 欧美在线视频免费| 国产一区久久精品| 日韩av网址在线| 91一区二区视频| 精品久久久中文| 亚洲视频重口味| 99天天综合性| 五月激情五月婷婷| 99在线精品视频在线观看| 亚洲精品美女久久7777777| 久久久久久久久久久久电影| 欧美在线免费观看| 在线欧美三级| 国产香蕉97碰碰久久人人| 亚洲第一视频在线播放| 91福利在线看| 国产精品免费av一区二区| 国产精品乱子久久久久| 天堂www中文在线资源| 毛片一区二区三区| 浮妇高潮喷白浆视频| 天天超碰亚洲| 日本公妇乱淫免费视频一区三区| 91在线一区| 成人黄色在线播放| 另类专区亚洲| 久久久久亚洲精品| 黄色免费在线看| 有码中文亚洲精品| 欧洲综合视频| 亚洲精品720p| 性生活免费网站| 欧美猛男超大videosgay| 久久久久久久久久成人| 亚洲高清中文字幕| 欧美卡一卡二卡三| 中文字幕日本不卡| 2017亚洲天堂| 国产色爱av资源综合区| 日本xxx在线播放| av不卡在线观看| 制服丝袜av在线| 国产成人免费视频精品含羞草妖精| 日本人视频jizz页码69| 久久久久久久波多野高潮日日| 九九爱精品视频| 好看的日韩av电影| 亚洲爆乳无码精品aaa片蜜桃| 婷婷亚洲图片| 国产一区一区三区| 亚州av乱码久久精品蜜桃| 五月婷婷一区| 日本不卡免费一区| 视频一区在线免费观看| 精品视频久久| 亚洲一区二区免费视频软件合集| 精品久久久久久久久久久aⅴ| 久久久久久久免费| 亚洲小说图片| 日韩三级电影网站| av资源久久| 亚洲 国产 日韩 综合一区| 国产剧情一区| 日本不卡高清视频一区| 精品产国自在拍| 西游记1978| 欧美电影免费观看高清| 欧美 另类 交| 国产精品啊v在线| 男人添女人荫蒂免费视频| 91久久综合| 欧美色图另类小说| 日本亚洲三级在线| xxx国产在线观看| 国精产品一区一区三区mba视频 | 三上悠亚激情av一区二区三区 | 中文字幕在线亚洲三区| 久久久久久久久久久9不雅视频| 天天综合五月天| 欧美日韩亚洲三区| aa视频在线播放| 久久精品动漫| 99国产精品久久久久久| 懂色av中文字幕一区二区三区| 欧美夫妇交换xxx| 国产性做久久久久久| 亚洲欧洲综合网| 亚洲在线视频网站| 日韩精品一区不卡| 在线不卡中文字幕| 日本美女一级视频| 影音先锋欧美精品| 欧美高清另类hdvideosexjaⅴ| 91av在线免费观看| 国产麻豆一区| 国产精品视频免费一区二区三区| 国产伦精品一区二区三区视频| 一区二区三区国| 亚洲激情另类| 日本美女高潮视频| 福利视频网站一区二区三区| 少妇久久久久久久久久| 亚洲四区在线观看| av网站中文字幕| 欧美一区二区三区视频在线| 天堂a√在线| 久久国产精品久久久久久久久久| 国产伦久视频在线观看| 成人精品一区二区三区电影黑人| 久久婷婷国产| 五月天男人天堂| 天堂一区二区在线| 精品人妻二区中文字幕| 国产精品美女久久久久久久久 | 国产精品黄色| 久久久精品三级| 成人高清免费观看| 国产精品69久久久久孕妇欧美| 天天做天天摸天天爽国产一区| 国产视频一区二区三区四区五区| 亚洲毛片在线观看| 国产理论电影在线| 91色琪琪电影亚洲精品久久| 九九精品在线| 免费在线观看视频a| 国内精品久久久久影院色| 黄瓜视频污在线观看| 亚洲综合精品久久| 一级特黄aa大片| 亚洲色图第一页| 成av人片在线观看www| 亚洲aⅴ男人的天堂在线观看| 狠狠做六月爱婷婷综合aⅴ| 国产九九九九九| 国产精品456| 欧美激情精品久久久久久免费| 欧美伊人久久久久久久久影院| 亚洲人成色777777精品音频| 久久久免费精品| 91精品入口| 男女爱爱视频网站| 精品一区二区三区蜜桃| 美国一级黄色录像| 欧洲一区在线电影| 蜜桃视频在线观看网站| 992tv在线成人免费观看| 风间由美一区二区av101| 强开小嫩苞一区二区三区网站| 久久激情五月激情| 激情高潮到大叫狂喷水| 在线观看亚洲精品| 成人在线观看免费| 国产精品日日做人人爱| 精品黄色一级片| 三上悠亚av一区二区三区| 日本一区二区三区国色天香 | 91丝袜脚交足在线播放| 欧美一区二区三区久久精品茉莉花| 亚洲美女爱爱视频| 亚洲视频在线一区二区| 国产免费一区二区三区最新不卡 | 毛片在线网站| 久久精品日产第一区二区三区精品版| 亚洲日本成人| 久久午夜夜伦鲁鲁片| 动漫精品一区二区| 日韩精品一二| 国产精品久久久久久网站| 日韩在线观看| 四虎成人在线播放| 亚洲精品国久久99热| 亚洲精品久久久蜜桃动漫| 久久琪琪电影院| 日韩在线你懂的| 9久久婷婷国产综合精品性色| 中文字幕第一区| www.黄色国产| 性色av一区二区三区免费| 免费视频国产一区| 激情五月俺来也| 怡红院av一区二区三区| 天堂av资源网| 国产精品欧美日韩久久| 亚洲高清影视| 中文字幕天堂网| 91国内精品野花午夜精品| 日本中文字幕视频在线| 高清不卡一区二区三区| 久久精品免费| 黄色录像二级片| 亚洲精品456在线播放狼人| 忘忧草在线www成人影院| 五月天色婷婷综合| 99久久精品免费看国产免费软件| 免费视频网站在线观看入口| 日韩三级成人av网| 久久a爱视频| 波多野结衣xxxx| 亚洲一区二区三区四区五区黄| 九色视频在线播放| 亚洲aa在线观看| 久久久一二三| 91麻豆精品成人一区二区| 亚洲国产美女久久久久 | 114国产精品久久免费观看| 欧美亚洲三级| 91porn在线视频| 国产性色av一区二区| 懂色av一区二区|