1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        最棒 Spring Boot 干貨總結(jié) !3萬字長文 ,收藏!

        共 15531字,需瀏覽 32分鐘

         ·

        2020-07-30 17:29


        來源:33h.co/eLt2


        前言:本文非常長,建議先mark后看

        說明:前面有4個小節(jié)關(guān)于Spring的基礎(chǔ)知識

        分別是:IOC容器、JavaConfig、事件監(jiān)聽、SpringFactoriesLoader詳解

        它們占據(jù)了本文的大部分內(nèi)容

        雖然它們之間可能沒有太多的聯(lián)系,但這些知識對于理解Spring Boot的核心原理至關(guān)重要,如果你對Spring框架爛熟于心,完全可以跳過這4個小節(jié)。正是因為這個系列的文章是由這些看似不相關(guān)的知識點組成,因此取名知識清單。

        在過去兩三年的Spring生態(tài)圈,最讓人興奮的莫過于Spring Boot框架。或許從命名上就能看出這個框架的設(shè)計初衷:快速的啟動Spring應(yīng)用。因而Spring Boot應(yīng)用本質(zhì)上就是一個基于Spring框架的應(yīng)用,它是Spring對“約定優(yōu)先于配置”理念的最佳實踐產(chǎn)物,它能夠幫助開發(fā)者更快速高效地構(gòu)建基于Spring生態(tài)圈的應(yīng)用。

        那Spring Boot有何魔法?

        自動配置、起步依賴、Actuator、命令行界面(CLI) 是Spring Boot最重要的4大核心特性,其中CLI是Spring Boot的可選特性,雖然它功能強大,但也引入了一套不太常規(guī)的開發(fā)模型,因而這個系列的文章僅關(guān)注其它3種特性。如文章標題,本文是這個系列的第一部分,將為你打開Spring Boot的大門,重點為你剖析其啟動流程以及自動配置實現(xiàn)原理。要掌握這部分核心內(nèi)容,理解一些Spring框架的基礎(chǔ)知識,將會讓你事半功倍。

        一、拋磚引玉:探索Spring IoC容器

        如果有看過SpringApplication.run()方法的源碼,Spring Boot冗長無比的啟動流程一定

        會讓你抓狂,透過現(xiàn)象看本質(zhì).

        SpringApplication只是將一個典型的Spring應(yīng)用的啟動流程進行了擴展,因此,透徹理解,?Spring容器是打開Spring Boot大門的一把鑰匙。

        1.1、Spring IoC容器

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

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

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

        BeanDefinition對象就承擔了這個責任:容器中的每一個bean都會有一個對應(yīng)的BeanDefinition實例,該實例負責保存bean對象的所有必要信息,包括bean對象的class類型、是否是抽象類、構(gòu)造方法和參數(shù)、其它屬性等等。

        當客戶端向容器請求相應(yīng)對象時,容器就會通過這些信息為客戶端返回一個完整可用的bean實例。

        原材料已經(jīng)準備好(把BeanDefinition看著原料),開始做菜吧,等等,你還需要一份菜譜,BeanDefinitionRegistry和BeanFactory就是這份菜譜,BeanDefinitionRegistry抽象出bean的注冊邏輯,而BeanFactory則抽象出了bean的管理邏輯,而各個BeanFactory的實現(xiàn)類就具體承擔了bean的注冊以及管理工作。

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

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


        DefaultListableBeanFactory作為一個比較通用的BeanFactory實現(xiàn),它同時也實現(xiàn)了BeanDefinitionRegistry接口,因此它就承擔了Bean的注冊管理工作。從圖中也可以看出,BeanFactory接口中主要包含getBean、containBean、getType、getAliases等管理bean的方法,而BeanDefinitionRegistry接口則包含registerBeanDefinition、removeBeanDefinition、getBeanDefinition等注冊管理BeanDefinition的方法。

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

        // 默認容器實現(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);
        ?// 如果有多個bean,還可以指定各個bean之間的依賴關(guān)系
        ?// ........

        ??// 然后可以從容器中獲取這個bean的實例
        ?// 注意:這里的beanRegistry其實實現(xiàn)了BeanFactory接口,所以可以強轉(zhuǎn),
        ?// 單純的BeanDefinitionRegistry是無法強制轉(zhuǎn)換到BeanFactory類型的
        ?BeanFactory container = (BeanFactory)beanRegistry;
        ?Business business = (Business)container.getBean("beanName");
        這段代碼僅為了說明BeanFactory底層的大致工作流程.實際情況會更加復雜,比如bean之間的依賴關(guān)系可能定義在外部配置文件(XML/Properties)中、也可能是注解方式。
        Spring IoC容器的整個工作流程大致可以分為兩個階段:
        ①、容器啟動階段
        容器啟動時,會通過某種途徑加載Configuration MetaData。除了代碼方式比較直接外,在大部分情況下,容器需要依賴某些工具類,比如:BeanDefinitionReader,BeanDefinitionReader會對加載的Configuration MetaData進行解析和分析,并將分析后的信息組裝為相應(yīng)的BeanDefinition,最后把這些保存了bean定義的BeanDefinition,注冊到相應(yīng)的BeanDefinitionRegistry,這樣容器的啟動工作就完成了。
        這個階段主要完成一些準備性工作,更側(cè)重于bean對象管理信息的收集,當然一些驗證性或者輔助性的工作也在這一階段完成。
        來看一個簡單的例子吧,過往,所有的bean都定義在XML配置文件中,下面的代碼將模擬
        BeanFactory如何從配置文件中加載bean的定義以及依賴關(guān)系:
        // 通常為BeanDefinitionRegistry的實現(xiàn)類,這里以DeFaultListabeBeanFactory為例
        ?BeanDefinitionRegistry beanRegistry = new?DefaultListableBeanFactory();
        ??// XmlBeanDefinitionReader實現(xiàn)了BeanDefinitionReader接口,用于解析XML文件
        ?XmlBeanDefinitionReader beanDefinitionReader = new?XmlBeanDefinitionReaderImpl(beanRegistry);
        ?// 加載配置文件 beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");
        ??// 從容器中獲取bean實例
        ?BeanFactory container = (BeanFactory)beanRegistry;
        ?Business business = (Business)container.getBean("beanName");
        ②、Bean的實例化階段
        經(jīng)過第一階段,所有bean定義都通過BeanDefinition的方式注冊到BeanDefinitionRegistry中當某個請求通過容器的getBean方法請求某個對象,或者因為依賴關(guān)系容器需要隱式的調(diào)用getBean時,就會觸發(fā)第二階段的活動:容器會首先檢查所請求的對象之前是否已經(jīng)實例化完成。
        如果沒有,則會根據(jù)注冊的BeanDefinition所提供的信息實例化被請求對象,并為其注入依賴。
        當該對象裝配完畢后,容器會立即將其返回給請求方法使用。BeanFactory只是Spring IoC容器的一種實現(xiàn),如果沒有特殊指定,它采用采用延遲初始化策略:只有當訪問容器中的某個對象時,才對該對象進行初始化和依賴注入操作。
        而在實際場景下,我們更多的使用另外一種類型的容器:ApplicationContext,它構(gòu)建在BeanFactory之上,屬于更高級的容器,除了具有BeanFactory的所有能力之外,還提供對事件監(jiān)聽機制以及國際化的支持等。它管理的bean,在容器啟動時全部完成初始化和依賴注入操作。
        1.2、Spring容器擴展機制
        IoC容器負責管理容器中所有bean的生命周期,而在bean生命周期的不同階段,Spring提供了不同的擴展點來改變bean的命運。在容器的啟動階段,BeanFactoryPostProcessor允許我們在容器實例化相應(yīng)對象之前,對注冊到容器的BeanDefinition所保存的信息做一些額外的操作,比如修改bean定義的某些屬性或者增加其他信息等。
        如果要自定義擴展類,通常需要實現(xiàn).
        org.springframework.beans.factory.config.BeanFactoryPostProcessor接口,與此同時,因為容器中可能有多個BeanFactoryPostProcessor,可能還需要實現(xiàn)org.springframework.core.Ordered接口,以保證BeanFactoryPostProcessor按照順序執(zhí)行。
        Spring提供了為數(shù)不多的BeanFactoryPostProcessor實現(xiàn).我們以PropertyPlaceholderConfigurer來說明其大致的工作流程。
        在Spring項目的XML配置文件中,經(jīng)??梢钥吹皆S多配置項的值使用占位符,而將占位符所代表的值單獨配置到獨立的properties文件,這樣可以將散落在不同XML文件中的配置集中管理,而且也方便運維根據(jù)不同的環(huán)境進行配置不同的值。
        這個非常實用的功能就是由PropertyPlaceholderConfigurer負責實現(xiàn)的。
        根據(jù)前文,當BeanFactory在第一階段加載完所有配置信息時,BeanFactory中保存的對象的屬性還是以占位符方式存在的,比如${jdbc.mysql.url}。
        當PropertyPlaceholderConfigurer作為BeanFactoryPostProcessor被應(yīng)用時,它會使用properties配置文件中的值來替換相應(yīng)的BeanDefinition中占位符所表示的屬性值。當需要實例化bean時,bean定義中的屬性值就已經(jīng)被替換成我們配置的值。當然其實現(xiàn)比上面描述的要復雜一些,這里僅說明其大致工作原理,更詳細的實現(xiàn)可以參考其源碼。
        與之相似的,還有BeanPostProcessor,其存在于對象實例化階段。跟BeanFactoryPostProcessor類似,它會處理容器內(nèi)所有符合條件并且已經(jīng)實例化后的對象。
        簡單的對比,BeanFactoryPostProcessor處理bean的定義,而BeanPostProcessor則處理bean完成實例化后的對象。
        BeanPostProcessor定義了兩個接口:
        // 通常為BeanDefinitionRegistry的實現(xiàn)類,這里以DeFaultListabeBeanFactory為例
        ?BeanDefinitionRegistry beanRegistry = new?DefaultListableBeanFactory();
        ??// XmlBeanDefinitionReader實現(xiàn)了BeanDefinitionReader接口,用于解析XML文件
        ?XmlBeanDefinitionReader beanDefinitionReader = new?XmlBeanDefinitionReaderImpl(beanRegistry);
        ?// 加載配置文件 beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");
        ??// 從容器中獲取bean實例
        ?BeanFactory container = (BeanFactory)beanRegistry;
        ?Business business = (Business)container.getBean("beanName");
        為了理解這兩個方法執(zhí)行的時機,簡單的了解下bean的整個生命周期:

        Bean的實例化過程(來自:Spring揭秘)
        postProcessBeforeInitialization()方法與postProcessAfterInitialization()分別對應(yīng)圖中前置處理和后置處理兩個步驟將執(zhí)行的方法。這兩個方法中都傳入了bean對象實例的引用,為擴展容器的對象實例化過程提供了很大便利,在這兒幾乎可以對傳入的實例執(zhí)行任何操作。
        注解、AOP等功能的實現(xiàn)均大量使用了BeanPostProcessor,比如有一個自定義注解,你完全可以實現(xiàn)BeanPostProcessor的接口,在其中判斷bean對象的腦袋上是否有該注解,如果有,你可以對這個bean實例執(zhí)行任何操作,想想是不是非常的簡單?
        再來看一個更常見的例子,在Spring中經(jīng)常能夠看到各種各樣的Aware接口,其作用就是在對象實例化完成以后將Aware接口定義中規(guī)定的依賴注入到當前實例中。
        比如最常見的ApplicationContextAware接口,實現(xiàn)了這個接口的類都可以獲取到一個ApplicationContext對象。當容器中每個對象的實例化過程走到BeanPostProcessor前置處理這一步時,容器會檢測到之前注冊到容器的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);
        ?????}
        ?????// ......
        }
        最后總結(jié)一下,本小節(jié)內(nèi)容和你一起回顧了Spring容器的部分核心內(nèi)容,限于篇幅不能寫更多,但理解這部分內(nèi)容,足以讓您輕松理解Spring Boot的啟動原理,如果在后續(xù)的學習過程中遇到一些晦澀難懂的知識,再回過頭來看看Spring的核心知識,也許有意想不到的效果。
        也許Spring Boot的中文資料很少,但Spring的中文資料和書籍有太多太多,總有東西能給你啟發(fā)。
        二、夯實基礎(chǔ):JavaConfig與常見Annotation
        2.1、JavaConfig
        我們知道bean是Spring IOC中非常核心的概念,Spring容器負責bean的生命周期的管理。
        在最初,Spring使用XML配置文件的方式來描述bean的定義以及相互間的依賴關(guān)系,但隨著Spring的發(fā)展,越來越多的人對這種方式表示不滿,因為Spring項目的所有業(yè)務(wù)類均以bean的形式配置在XML文件中,造成了大量的XML文件,使項目變得復雜且難以管理。
        后來,基于純Java Annotation依賴注入框架Guice出世,其性能明顯優(yōu)于采用XML方式的Spring,甚至有部分人認為,Guice可以完全取代Spring(Guice僅是一個輕量級IOC框架,取代Spring還差的挺遠).
        正是這樣的危機感,促使Spring及社區(qū)推出并持續(xù)完善了JavaConfig子項目,它基于Java代碼和Annotation注解來描述bean之間的依賴綁定關(guān)系。
        比如,下面是使用XML配置方式來描述bean的定義:
        "bookService"?class="cn.moondev.service.BookServiceImpl">bean>
          而基于JavaConfig的配置形式是這樣的:
          @Configuration
          ?public?class
          ?MoonBookConfiguration
          ?
          {
          ??????// 任何標志了@Bean的方法,其返回值將作為一個bean注冊到Spring的IOC容器中
          ?????// 方法名默認成為該bean定義的id
          ?????@Bean
          ?????public?BookService bookService()?{
          ?????????return?new?BookServiceImpl();
          ?????}
          ?}
          如果兩個bean之間有依賴關(guān)系的話,在XML配置中應(yīng)該是這樣:
          "bookService"?class="cn.moondev.service.BookServiceImpl">
          ?????<property?name="dependencyService"?ref="dependencyService"/>
          ?bean>

          ??"otherService"?class="cn.moondev.service.OtherServiceImpl">
          ?????<property?name="dependencyService"?ref="dependencyService"/>
          ?bean>

          ??"dependencyService"?class="DependencyServiceImpl"/>
          而在JavaConfig中則是這樣:
          @Configuration
          ?public?class?MoonBookConfiguration?{
          ??????// 如果一個bean依賴另一個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();
          ?????}
          ?}
          你可能注意到這個示例中,有兩個bean都依賴于dependencyService,也就是說當初始化bookService時會調(diào)用dependencyService(),在初始化otherService時也會調(diào)用dependencyService(),那么問題來了?
          這時候IOC容器中是有一個dependencyService實例還是兩個?這個問題留著大家思考吧,這里不再贅述。
          2.2、@ComponentScan
          @ComponentScan注解對應(yīng)XML配置形式中的元素表示啟用組件掃描,Spring會自動掃描所有通過注解配置的bean,然后將其注冊到IOC容器中。
          我們可以通過basePackages等屬性來指定@ComponentScan自動掃描的范圍,如果不指定,默認從聲明@ComponentScan所在類的package進行掃描。正因為如此,SpringBoot的啟動類都默認在src/main/java下。
          2.3、@Import
          @Import注解用于導入配置類,舉個簡單的例子:
          @Configuration
          ?public?class?MoonBookConfiguration{
          ?????@Bean
          ?????public?BookService bookService()?{
          ?????????return?new?BookServiceImpl();
          ?????}
          ?}
          現(xiàn)在有另外一個配置類,比如:MoonUserConfiguration,這個配置類中有一個bean依賴于MoonBookConfiguration中的bookService,如何將這兩個bean組合在一起?
          借助@Import即可:
          @Configuration
          ?// 可以同時導入多個配置類,比如:@Import({A.class,B.class})
          ?@Import(MoonBookConfiguration.class)
          ?public class MoonUserConfiguration
          ?{
          ?????@Bean
          ?????public UserService userService(BookService bookService) {
          ?????????return?new?BookServiceImpl(bookService);
          ?????}
          ?}
          需要注意的是,在4.2之前,@Import注解只支持導入配置類,但是在4.2之后,它支持導入普通類,并將這個類作為一個bean的定義注冊到IOC容器中。

          2.4、@Conditional
          @Conditional注解表示在滿足某種條件后才初始化一個bean或者啟用某些配置。
          它一般用在由@Component、@Service、@Configuration等注解標識的類上面,或者由@Bean標記的方法上。如果一個@Configuration類標記了@Conditional,則該類中所有標識了@Bean的方法和@Import注解導入的相關(guān)類將遵從這些條件。
          在Spring里可以很方便的編寫你自己的條件類,所要做的就是實現(xiàn)Condition接口,并覆蓋它的matches()方法。
          舉個例子,下面的簡單條件類表示只有在Classpath里存在JdbcTemplate類時才生效:
          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;
          ?????}
          ?}
          當你用Java來聲明bean的時候,可以使用這個自定義條件類:
          @Conditional(JdbcTemplateCondition.class) @Service?public MyService service() { ...... }
          這個例子中只有當JdbcTemplateCondition類的條件成立時才會創(chuàng)建MyService這個bean。
          也就是說MyService這bean的創(chuàng)建條件是classpath里面包含JdbcTemplate,否則這個bean的聲明就會被忽略掉。
          Spring Boot定義了很多有趣的條件,并把他們運用到了配置類上,這些配置類構(gòu)成了Spring?Boot的自動配置的基礎(chǔ)。
          Spring Boot運用條件化配置的方法是:定義多個特殊的條件化注解,并將它們用到配置類上。
          下面列出了Spring Boot提供的部分條件化注解:

          2.5、@ConfigurationProperties與@EnableConfigurationProperties
          當某些屬性的值需要配置的時候,我們一般會在application.properties文件中新建配置項,然后在bean中使用@Value注解來獲取配置的值,比如下面配置數(shù)據(jù)源的代碼。
          // 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);
          ?????}
          ?}
          使用@Value注解注入的屬性通常都比較簡單,如果同一個配置在多個地方使用,也存在不方便維護的問題(考慮下,如果有幾十個地方在使用某個配置,而現(xiàn)在你想改下名字,你改怎么做?)
          對于更為復雜的配置,Spring Boot提供了更優(yōu)雅的實現(xiàn)方式,那就是@ConfigurationProperties注解。
          我們可以通過下面的方式來改寫上面的代碼:
          @Component
          ?// 還可以通過@PropertySource("classpath:jdbc.properties")來指定配置文件
          ?@ConfigurationProperties("jdbc.mysql")
          ?// 前綴=jdbc.mysql,會在配置文件中尋找jdbc.mysql.*的配置項
          ?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對于更為復雜的配置,處理起來也是得心應(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
          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;
          ?????}
          ?}
          @EnableConfigurationProperties注解表示對@ConfigurationProperties的內(nèi)嵌支持默認會將對應(yīng)Properties Class作為bean注入的IOC容器中,即在相應(yīng)的Properties類上不用加@Component注解。
          三、削鐵如泥:SpringFactoriesLoader詳解
          JVM提供了3種類加載器:
          BootstrapClassLoader、ExtClassLoader、AppClassLoader分別加載Java核心類庫、擴展類庫以及應(yīng)用的類路徑(CLASSPATH)下的類庫。
          JVM通過雙親委派模型進行類的加載,我們也可以通過繼承java.lang.classloader實現(xiàn)自己的類加載器。
          何為雙親委派模型?當一個類加載器收到類加載任務(wù)時,會先交給自己的父加載器去完成,因此最終加載任務(wù)都會傳遞到最頂層的BootstrapClassLoader,只有當父加載器無法完成加載任務(wù)時,才會嘗試自己來加載。
          采用雙親委派模型的一個好處是保證使用不同類加載器最終得到的都是同一個對象,這樣就可以保證Java 核心庫的類型安全,比如,加載位于rt.jar包中的java.lang.Object類,不管是哪個加載器加載這個類,最終都是委托給頂層的BootstrapClassLoader來加載的,這樣就可以保證任何的類加載器最終得到的都是同樣一個Object對象。
          查看ClassLoader的源碼,對雙親委派模型會有更直觀的認識:
          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ā)者自己實現(xiàn)的,也就是說
          ?????????????// 自定義類加載器時,重寫此方法即可
          ????????????c = findClass(name);
          ?????????}
          ?????}
          ?????if?(resolve) {
          ????????resolveClass(c);
          ?????}
          ?????return?c;
          ?????}
          ?}
          但雙親委派模型并不能解決所有的類加載器問題,比如,Java 提供了很多服務(wù)提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實現(xiàn)。
          常見的 SPI 有 JDBC、JNDI、JAXP 等,這些SPI的接口由核心類庫提供,卻由第三方實現(xiàn)這樣就存在一個問題:SPI 的接口是 Java 核心庫的一部分,是由BootstrapClassLoader加載的;SPI實現(xiàn)的Java類一般是由AppClassLoader來加載的。BootstrapClassLoader是無法找到 SPI 的實現(xiàn)類的,因為它只加載Java的核心庫。它也不能代理給AppClassLoader,因為它是最頂層的類加載器。也就是說,雙親委派模型并不能解決這個問題。
          線程上下文類加載器(ContextClassLoader)正好解決了這個問題。
          從名稱上看,可能會誤解為它是一種新的類加載器,實際上,它僅僅是Thread類的一個變量而已,可以通過setContextClassLoader(ClassLoader cl)和getContextClassLoader()來設(shè)置和獲取該對象。
          如果不做任何的設(shè)置,Java應(yīng)用的線程的上下文類加載器默認就是AppClassLoader。
          在核心類庫使用SPI接口時,傳遞的類加載器使用線程上下文類加載器,就可以成功的加載到SPI實現(xiàn)的類。

          線程上下文類加載器在很多SPI的實現(xiàn)中都會用到。但在JDBC中,你可能會看到一種更直接的實現(xiàn)方式,比如,JDBC驅(qū)動管理java.sql.Driver中的loadInitialDrivers()方法中
          你可以直接看到JDK是如何加載驅(qū)動的:
          for?(String?aDriver : driversList) {
          ?????try?{
          ?????????// 直接使用AppClassLoader
          ?????????Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
          ?????} catch?(Exception ex) {
          ?????????println("DriverManager.Initialize: load failed: "?+ ex);
          ??????}
          ???}
          其實講解線程上下文類加載器,最主要是讓大家在看到:
          Thread.currentThread().getClassLoader()Thread.currentThread().getContextClassLoader()
          時不會一臉懵逼.
          這兩者除了在許多底層框架中取得的ClassLoader可能會有所不同外,其他大多數(shù)業(yè)務(wù)場景下都是一樣的,大家只要知道它是為了解決什么問題而存在的即可。
          類加載器除了加載class外,還有一個非常重要功能,就是加載資源,它可以從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);
          ?????}
          ?????tmp[1] = findResources(name);
          ?????return?new?CompoundEnumeration<>(tmp);
          ?}
          是不是覺得有點眼熟,不錯,它的邏輯其實跟類加載的邏輯是一樣的,首先判斷父類加載器是否為空,不為空則委托父類加載器執(zhí)行資源查找任務(wù), 直到BootstrapClassLoader,最后才輪到自己查找。
          而不同的類加載器負責掃描不同路徑下的jar包,就如同加載class一樣,最后會掃描所有的jar包,找到符合條件的資源文件。
          類加載器的findResources(name)方法會遍歷其負責加載的所有jar包,找到j(luò)ar包中名稱為name的資源文件,這里的資源可以是任何文件,甚至是.class文件,比如下面的示例,用于查找Array.class文件:
          // 尋找Array.class文件
          ?public?static?void?main(String[] args) throws Exception{
          ?????// Array.class的完整路徑
          ?????String name = "java/sql/Array.class";
          ?????Enumeration urls = Thread.currentThread().getContextClassLoader().getResources(name);
          ?????while?(urls.hasMoreElements()) {
          ?????????URL url = urls.nextElement();
          ?????????System.out.println(url.toString());
          ?????}
          ?}
          運行后可以得到如下結(jié)果:
          $JAVA_HOME/jre/lib/rt.jar!/java/sql/Array.class
          根據(jù)資源文件的URL,可以構(gòu)造相應(yīng)的文件來讀取資源內(nèi)容。
          看到這里,你可能會感到挺奇怪的,你不是要詳解SpringFactoriesLoader嗎?
          上來講了一ClassLoader是幾個意思?看下它的源碼你就知道了:
          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 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下的每個Jar包中搜尋所有META-INF/spring.factories配置文件,然后將解析properties文件,找到指定名稱的配置后返回。
          需要注意的是,其實這里不僅僅是會去ClassPath路徑下查找,會掃描所有路徑下的Jar包,只不過這個文件只會在Classpath下的jar包中。
          來簡單看下spring.factories文件的內(nèi)容吧:
          // 來自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
          ?// EnableAutoConfiguration后文會講到,它用于開啟Spring Boot自動配置功能
          ?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類,我們就可以通過反射實例化這些類然后注入到IOC容器中,最后容器里就有了一系列標注了@Configuration的JavaConfig形式的配置類。
          這就是SpringFactoriesLoader,它本質(zhì)上屬于Spring框架私有的一種擴展方案,類似于SPI,Spring Boot在Spring基礎(chǔ)上的很多核心功能都是基于此,希望大家可以理解。
          四、另一件武器:Spring容器的事件監(jiān)聽機制
          過去,事件監(jiān)聽機制多用于圖形界面編程,比如:點擊按鈕、在文本框輸入內(nèi)容等操作被稱為事件,而當事件觸發(fā)時,應(yīng)用程序作出一定的響應(yīng)則表示應(yīng)用監(jiān)聽了這個事件,而在服務(wù)器端,事件的監(jiān)聽機制更多的用于異步通知以及監(jiān)控和異常處理。
          Java提供了實現(xiàn)事件監(jiān)聽機制的兩個基礎(chǔ)類:自定義事件類型擴展自java.util.EventObject、事件的監(jiān)聽器擴展自java.util.EventListener。
          來看一個簡單的實例:簡單的監(jiān)控一個方法的耗時。
          首先定義事件類型,通常的做法是擴展EventObject,隨著事件的發(fā)生,相應(yīng)的狀態(tài)通常都封裝在此類中:
          public?class?MethodMonitorEvent?extends?EventObject?{
          ?????// 時間戳,用于記錄方法開始執(zhí)行的時間
          ?????public?long?timestamp;

          ??????public?MethodMonitorEvent(Object source)?{
          ?????????super(source);
          ?????}
          ?}
          事件發(fā)布之后,相應(yīng)的監(jiān)聽器即可對該類型的事件進行處理,我們可以在方法開始執(zhí)行之前發(fā)布一個begin事件.
          在方法執(zhí)行結(jié)束之后發(fā)布一個end事件,相應(yīng)地,事件監(jiān)聽器需要提供方法對這兩種情況下接收到的事件進行處理:
          // 1、定義事件監(jiān)聽接口
          ?public?interface?MethodMonitorEventListener?extends?EventListener?{
          ?????// 處理方法執(zhí)行之前發(fā)布的事件
          ?????public?void?onMethodBegin(MethodMonitorEvent event);
          ?????// 處理方法結(jié)束時發(fā)布的事件
          ?????public?void?onMethodEnd(MethodMonitorEvent event);
          ?}
          ?// 2、事件監(jiān)聽接口的實現(xiàn):如何處理
          ?public?class?AbstractMethodMonitorEventListener?implements?MethodMonitorEventListener?{
          ??????@Override
          ?????public?void?onMethodBegin(MethodMonitorEvent event)
          {
          ?????????// 記錄方法開始執(zhí)行時的時間
          ?????????event.timestamp = System.currentTimeMillis();
          ?????}
          ??????@Override
          ?????public?void?onMethodEnd(MethodMonitorEvent event)
          {
          ?????????// 計算方法耗時
          ?????????long?duration = System.currentTimeMillis() - event.timestamp;
          ?????????System.out.println("耗時:"?+ duration);
          ?????}
          ?}
          事件監(jiān)聽器接口針對不同的事件發(fā)布實際提供相應(yīng)的處理方法定義,最重要的是,其方法只接收MethodMonitorEvent參數(shù),說明這個監(jiān)聽器類只負責監(jiān)聽器對應(yīng)的事件并進行處理。
          有了事件和監(jiān)聽器,剩下的就是發(fā)布事件,然后讓相應(yīng)的監(jiān)聽器監(jiān)聽并處理。
          通常情況,我們會有一個事件發(fā)布者,它本身作為事件源,在合適的時機,將相應(yīng)的事件發(fā)布給對應(yīng)的事件監(jiān)聽器:
          public?class?MethodMonitorEventPublisher?{

          ??????private?List listeners = new?ArrayList();

          ??????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)聽器被移除,這里為了安全做一個復制操作
          ?????????List copyListeners = ? new?ArrayList(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();
          ?????}
          ?????// 省略實現(xiàn)
          ?????public?void?addEventListener(MethodMonitorEventListener listener) {}
          ?????public?void?removeEventListener(MethodMonitorEventListener listener) {}
          ?????public?void?removeAllListeners() {}
          對于事件發(fā)布者(事件源)通常需要關(guān)注兩點:
          1. 在合適的時機發(fā)布事件。此例中的methodMonitor()方法是事件發(fā)布的源頭,其在方法執(zhí)行之前和結(jié)束之后兩個時間點發(fā)布MethodMonitorEvent事件,每個時間點發(fā)布的事件都會傳給相應(yīng)的監(jiān)聽器進行處理。
          在具體實現(xiàn)時需要注意的是,事件發(fā)布是順序執(zhí)行,為了不影響處理性能,事件監(jiān)聽器的處理邏輯應(yīng)盡量簡單。

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

          如果這里沒有提供remove方法,那么注冊的監(jiān)聽器示例將一直MethodMonitorEventPublisher引用,即使已經(jīng)廢棄不用了,也依然在發(fā)布者的監(jiān)聽器列表中,這會導致隱性的內(nèi)存泄漏。
          Spring容器內(nèi)的事件監(jiān)聽機制
          Spring的ApplicationContext容器內(nèi)部中的所有事件類型均繼承自org.springframework.context.AppliationEvent,容器中的所有監(jiān)聽器都實現(xiàn)org.springframework.context.ApplicationListener接口,并且以bean的形式注冊在容器中。
          一旦在容器內(nèi)發(fā)布ApplicationEvent及其子類型的事件,注冊到容器的ApplicationListener就會對這些事件進行處理。
          你應(yīng)該已經(jīng)猜到是怎么回事了。
          ApplicationEvent繼承自EventObject,Spring提供了一些默認的實現(xiàn),比如:
          ContextClosedEvent表示容器在即將關(guān)閉時發(fā)布的事件類型,ContextRefreshedEvent
          表示容器在初始化或者刷新的時候發(fā)布的事件類型......容器內(nèi)部使用ApplicationListener作為事件監(jiān)聽器接口定義,它繼承自EventListener。
          ApplicationContext容器在啟動時,會自動識別并加載EventListener類型的bean一旦容器內(nèi)有事件發(fā)布,將通知這些注冊到容器的EventListener。
          ApplicationContext接口繼承了ApplicationEventPublisher接口,該接口提供了void?publishEvent(ApplicationEvent event)方法定義,不難看出,ApplicationContext容器擔當?shù)木褪鞘录l(fā)布者的角色。
          如果有興趣可以查看AbstractApplicationContext.publishEvent(ApplicationEvent?event)方法的源碼:ApplicationContext將事件的發(fā)布以及監(jiān)聽器的管理工作委托給 ApplicationEventMulticaster接口的實現(xiàn)類。
          在容器啟動時,會檢查容器內(nèi)是否存在名為applicationEventMulticaster的?ApplicationEventMulticaster對象實例。
          如果有就使用其提供的實現(xiàn),沒有就默認初始化一個SimpleApplicationEventMulticaster作為實現(xiàn)。
          最后,如果我們業(yè)務(wù)需要在容器內(nèi)部發(fā)布事件,只需要為其注入ApplicationEventPublisher?依賴即可:實現(xiàn)ApplicationEventPublisherAware接口或者ApplicationContextAware接口.
          五、出神入化:揭秘自動配置原理
          典型的Spring Boot應(yīng)用的啟動類一般均位于src/main/java根路徑下
          比如MoonApplication類:
          @SpringBootApplication
          ?public?class?MoonApplication?{
          ??????public?static?void?main(String[] args)?{
          ?????????SpringApplication.run(MoonApplication.class, args);
          ?????}
          ?}
          其中@SpringBootApplication開啟組件掃描和自動配置,而SpringApplication.run則負責啟動引導應(yīng)用程序。
          @SpringBootApplication是一個復合Annotation,它將三個有用的注解組合在一起:
          @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 {
          ?????// ......
          ?}
          @SpringBootConfiguration就是@Configuration,它是Spring框架的注解,標明該類是一個JavaConfig配置類。
          而@ComponentScan啟用組件掃描,前文已經(jīng)詳細講解過,這里著重關(guān)注@EnableAutoConfiguration。@EnableAutoConfiguration注解表示開啟Spring Boot自動配置功能,Spring Boot會根據(jù)應(yīng)用的依賴、自定義的bean、classpath下有沒有某個類 等等因素來猜測你需要的bean,
          然后注冊到IOC容器中。
          那@EnableAutoConfiguration是如何推算出你的需求?
          首先看下它的定義:
          @Target(ElementType.TYPE)
          ?@Retention(RetentionPolicy.RUNTIME)
          ?@Documented
          ?@Inherited
          ?@AutoConfigurationPackage
          ?@Import(EnableAutoConfigurationImportSelector.class)
          ?public @interface?EnableAutoConfiguration {
          ?????// ......
          ?}
          你的關(guān)注點應(yīng)該在@Import(EnableAutoConfigurationImportSelector.class)上了,前文說過,@Import注解用于導入類,并將這個類作為一個bean的定義注冊到容器中,這里將把EnableAutoConfigurationImportSelector作為bean注入到容器中,而這個類會將所有符合條件的@Configuration配置都加載到容器中,看看它的代碼:
          public?String[] selectImports(AnnotationMetadata annotationMetadata) {
          ?????// 省略了大部分代碼,保留一句核心代碼
          ?????// 注意:SpringBoot最近版本中,這句代碼被封裝在一個單獨的方法中
          ?????// SpringFactoriesLoader相關(guān)知識請參考前文
          ?????List<String> factories = new?ArrayList<String>(new?LinkedHashSet<String>(
          ???????????SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader)));
          ?}
          這個類會掃描所有的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是如何自動配置的:
          @Configuration
          ?@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
          ?@EnableConfigurationProperties(DataSourceProperties.class)
          ?@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
          ?public class DataSourceAutoConfiguration {
          ?}
          分別說一說:
          @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }):當Classpath中存在DataSource或者EmbeddedDatabaseType類時才啟用這個配置,否則這個配置將被忽略。
          @EnableConfigurationProperties(DataSourceProperties.class):將DataSource的默認配置類注入到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 }):導入其他額外的配置,就以DataSourcePoolMetadataProvidersConfiguration為例吧。
          @Configuration
          ?public class DataSourcePoolMetadataProvidersConfiguration {

          ??????@Configuration
          ?????@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
          ?????static class TomcatDataSourcePoolMetadataProviderConfiguration {
          ?????????@Bean
          ?????????public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() {
          ?????????????.....
          ?????????}
          ?????}
          ???......
          ?}
          DataSourcePoolMetadataProvidersConfiguration是數(shù)據(jù)庫連接池提供者的一個配置類,即Classpath中存在org.apache.tomcat.jdbc.pool.DataSource.class,則使用tomcat-jdbc連接池,如果Classpath中存在HikariDataSource.class則使用Hikari連接池。
          這里僅描述了DataSourceAutoConfiguration的冰山一角,但足以說明Spring Boot如何利用條件話配置來實現(xiàn)自動配置的。

          回顧一下,@EnableAutoConfiguration中導入了EnableAutoConfigurationImportSelector類,而這個類的selectImports()通過SpringFactoriesLoader得到了大量的配置類,而每一個配置類則根據(jù)條件化配置來做出決策,以實現(xiàn)自動配置。
          整個流程很清晰,但漏了一個大問題:
          EnableAutoConfigurationImportSelector.selectImports()是何時執(zhí)行的?其實這個方法會在容器啟動過程中執(zhí)行:AbstractApplicationContext.refresh(),更多的細節(jié)在下一小節(jié)中說明。
          六、啟動引導:Spring Boot應(yīng)用啟動的秘密
          6.1 SpringApplication初始化
          SpringBoot整個啟動流程分為兩個步驟:初始化一個SpringApplication對象、執(zhí)行該對象的run方法??聪耂pringApplication的初始化流程,SpringApplication的構(gòu)造方法中調(diào)用initialize(Object[] sources)方法,其代碼如下:
          private?void?initialize(Object[] sources) {
          ??????if?(sources != null?&& sources.length > 0) {
          ??????????this.sources.addAll(Arrays.asList(sources));
          ??????}
          ??????// 判斷是否是Web項目
          ??????this.webEnvironment = deduceWebEnvironment();
          ??????setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
          ??????setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
          ??????// 找到入口類
          ??????this.mainApplicationClass = deduceMainApplicationClass();
          ?}
          初始化流程中最重要的就是通過SpringFactoriesLoader找到spring.factories文件中配置的ApplicationContextInitializer和ApplicationListener兩個接口的實現(xiàn)類名稱,以便后期構(gòu)造相應(yīng)的實例。
          ApplicationContextInitializer的主要目的是在ConfigurableApplicationContext做refresh之前,對ConfigurableApplicationContext實例做進一步的設(shè)置或處理。
          ConfigurableApplicationContext繼承自ApplicationContext,其主要提供了對ApplicationContext進行設(shè)置的能力。
          實現(xiàn)一個ApplicationContextInitializer非常簡單,因為它只有一個方法,但大多數(shù)情況下我們沒有必要自定義一個ApplicationContextInitializer,即便是Spring Boot框架,它默認也只是注冊了兩個實現(xiàn),畢竟Spring的容器已經(jīng)非常成熟和穩(wěn)定,你沒有必要來改變它。
          而ApplicationListener的目的就沒什么好說的了,它是Spring框架對Java事件監(jiān)聽機制的一種框架實現(xiàn),具體內(nèi)容在前文Spring事件監(jiān)聽機制這個小節(jié)有詳細講解。這里主要說說,如果你想為Spring Boot應(yīng)用添加監(jiān)聽器,該如何實現(xiàn)?
          Spring Boot提供兩種方式來添加自定義監(jiān)聽器:
          通過SpringApplication.addListeners(ApplicationListener... listeners)或者SpringApplication.setListeners(Collection> listeners)兩個方法來添加一個或者多個自定義監(jiān)聽器
          既然SpringApplication的初始化流程中已經(jīng)從spring.factories中獲取到ApplicationListener的實現(xiàn)類,那么我們直接在自己的jar包的META-INF/spring.factories文件中新增配置即可:
          org.springframework.context.ApplicationListener=\ cn.moondev.listeners.xxxxListener\
          關(guān)于SpringApplication的初始化,我們就說這么多。
          6.2 Spring Boot啟動流程.
          Spring Boot應(yīng)用的整個啟動流程都封裝在SpringApplication.run方法中,其整個流程真的是太長太長了,但本質(zhì)上就是在Spring容器啟動的基礎(chǔ)上做了大量的擴展,按照這個思路來看看
          源碼:
          public?ConfigurableApplicationContext run(String... args) {
          ?????????StopWatch stopWatch = new?StopWatch();
          ?????????stopWatch.start();
          ?????????ConfigurableApplicationContext context = null;
          ?????????FailureAnalyzers analyzers = null;
          ?????????configureHeadlessProperty();
          ?????????// ①
          ?????????SpringApplicationRunListeners listeners = getRunListeners(args);
          ?????????listeners.starting();
          ?????????try?{
          ?????????????// ②
          ApplicationArguments applicationArguments = new?DefaultApplicationArguments(args);
          ?????????????ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
          ?????????????// ③
          ?????????????Banner printedBanner = printBanner(environment);
          ?????????????// ④
          ?????????????context = createApplicationContext();
          ?????????????// ⑤
          ?????????????analyzers = new?FailureAnalyzers(context);
          ?????????????// ⑥
          ?????????????prepareContext(context, environment, listeners, applicationArguments,printedBanner);
          ?????????????// ⑦
          ??????????????refreshContext(context);
          ?????????????// ⑧
          ?????????????afterRefresh(context, applicationArguments);
          ?????????????// ⑨
          ?????????????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)用開始啟動了。
          SpringApplicationRunListeners其本質(zhì)上就是一個事件發(fā)布者,它在SpringBoot應(yīng)用啟動的不同時間點發(fā)布不同應(yīng)用事件類型(ApplicationEvent),如果有哪些事件監(jiān)聽者(ApplicationListener)對這些事件感興趣,則可以接收并且處理。
          還記得初始化流程中,SpringApplication加載了一系列ApplicationListener嗎?這個啟動流程中沒有發(fā)現(xiàn)有發(fā)布事件的代碼,其實都已經(jīng)在SpringApplicationRunListeners這兒實現(xiàn)了。
          簡單的分析一下其實現(xiàn)流程,首先看下SpringApplicationRunListener的源碼:
          public?interface?SpringApplicationRunListener?{

          ??????// 運行run方法時立即調(diào)用此方法,可以用戶非常早期的初始化工作
          ?????void?starting();

          ??????????// Environment準備好后,并且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);

          ??????// 當run方法結(jié)束之前調(diào)用
          ?????void?finished(ConfigurableApplicationContext
          ?context, Throwable exception
          )
          ;
          ??}

          SpringApplicationRunListener只有一個實現(xiàn)類:EventPublishingRunListener。
          ①處的代碼只會獲取到一個EventPublishingRunListener的實例
          我們來看看starting()方法的內(nèi)容:
          public?void?starting()?{
          ?????// 發(fā)布一個ApplicationStartedEvent
          ?????this.initialMulticaster.multicastEvent(new?ApplicationStartedEvent(this.application, this.args));
          ?}
          順著這個邏輯,你可以在②處的prepareEnvironment()方法的源碼中找到
          listeners.environmentPrepared(environment);
            即SpringApplicationRunListener接口的第二個方法,那不出你所料,environmentPrepared()又發(fā)布了另外一個事件ApplicationEnvironmentPreparedEvent。
            接下來會發(fā)生什么,就不用我多說了吧。
            ② 創(chuàng)建并配置當前應(yīng)用將要使用的Environment,Environment用于描述應(yīng)用程序當前的運行環(huán)境,其抽象了兩個方面的內(nèi)容:
            配置文件(profile)和屬性(properties),開發(fā)經(jīng)驗豐富的同學對這兩個東西一定不會陌生:不同的環(huán)境(eg:生產(chǎn)環(huán)境、預(yù)發(fā)布環(huán)境)可以使用不同的配置文件,而屬性則可以從配置文件、環(huán)境變量、命令行參數(shù)等來源獲取。
            因此,當Environment準備好后,在整個應(yīng)用的任何時候,都可以從Environment中獲取資源。
            總結(jié)起來,②處的兩句代碼,主要完成以下幾件事:
            判斷Environment是否存在,不存在就創(chuàng)建(如果是web項目就創(chuàng)建StandardServletEnvironment,否則創(chuàng)建StandardEnvironment)
            配置Environment:配置profile以及properties
            調(diào)用SpringApplicationRunListener的environmentPrepared()方法,通知事件監(jiān)聽者:應(yīng)用的Environment已經(jīng)準備好
            ③、SpringBoot應(yīng)用在啟動時會輸出這樣的東西:
            . ____??????????_????????????__?_?_
            ??/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
            ?( ( )\___ | '
            _?| '_|?| '_ \/ _` |?\ \ \ \
            ??\\/ ___)| |_)| |?| |?| || (_|?| ) ) ) )
            ???' |
            ____| .__|_| |_|_|?|_\__, |?/ / /?/
            ??=========|_|==============|___/=/_/_/_/
            ?:: Spring Boot :: (v1.5.6.RELEASE)
            如果想把這個東西改成自己的涂鴉,你可以研究以下Banner的實現(xiàn),這個任務(wù)就留給你們吧。
            ④、根據(jù)是否是web項目,來創(chuàng)建不同的ApplicationContext容器。
            ⑤、創(chuàng)建一系列FailureAnalyzer,創(chuàng)建流程依然是通過SpringFactoriesLoader獲取到所有實現(xiàn)FailureAnalyzer接口的class,然后在創(chuàng)建對應(yīng)的實例。FailureAnalyzer用于分析故障并提供相關(guān)診斷信息。
            ⑥、初始化ApplicationContext,主要完成以下工作:
            將準備好的Environment設(shè)置給ApplicationContext
            遍歷調(diào)用所有的ApplicationContextInitializer的initialize()方法來對已經(jīng)創(chuàng)建好的ApplicationContext進行進一步的處理
            調(diào)用SpringApplicationRunListener的contextPrepared()方法,通知所有的監(jiān)聽者:ApplicationContext已經(jīng)準備完畢
            將所有的bean加載到容器中
            調(diào)用SpringApplicationRunListener的contextLoaded()方法,通知所有的監(jiān)聽者:ApplicationContext已經(jīng)裝載完畢
            ⑦、調(diào)用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。
            從名字上理解為刷新容器,那何為刷新?就是插手容器的啟動,聯(lián)系一下第一小節(jié)的內(nèi)容。
            那如何刷新呢?且看下面代碼:
            // 摘自refresh()方法中一句代碼
            ?invokeBeanFactoryPostProcessors(beanFactory);
            看看這個方法的實現(xiàn):
            protected?void?invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)?{
            ?????PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
            ?????......
            ?}
            獲取到所有的BeanFactoryPostProcessor來對容器做一些額外的操作。
            BeanFactoryPostProcessor允許我們在容器實例化相應(yīng)對象之前,對注冊到容器的BeanDefinition所保存的信息做一些額外的操作。
            這里的getBeanFactoryPostProcessors()方法可以獲取到3個Processor:
            ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
            ?SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
            ?ConfigFileApplicationListener$PropertySourceOrderingPostProcessor
            不是有那么多BeanFactoryPostProcessor的實現(xiàn)類,為什么這兒只有這3個?
            因為在初始化流程獲取到的各種ApplicationContextInitializer和ApplicationListener中,只有上文3個做了類似于如下操作:
            public?void?initialize(ConfigurableApplicationContext context)?{
            ?????context.addBeanFactoryPostProcessor(new?ConfigurationWarningsPostProcessor(getChecks()));
            ?}
            然后你就可以進入到PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()方法了,這個方法除了會遍歷上面的3個BeanFactoryPostProcessor處理外,還會獲取類型為BeanDefinitionRegistryPostProcessor的bean:org.springframework.context.annotation.internalConfigurationAnnotationProcessor,對應(yīng)的Class為ConfigurationClassPostProcessor。ConfigurationClassPostProcessor用于解析處理各種注解,包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。當處理@import注解的時候,就會調(diào)用這一小節(jié)中的EnableAutoConfigurationImportSelector.selectImports()來完成自動配置功能。其他的這里不再多講,如果你有興趣,可以查閱參考資料6。
            ⑧、查找當前context中是否注冊有CommandLineRunner和ApplicationRunner,如果有則遍歷執(zhí)行它們。
            ⑨、執(zhí)行所有SpringApplicationRunListener的finished()方法。這就是Spring Boot的整個啟動流程,其核心就是在Spring容器初始化并啟動的基礎(chǔ)上加入各種擴展點,這些擴展點包括:ApplicationContextInitializer、ApplicationListener以及各種BeanFactoryPostProcessor等等。
            你對整個流程的細節(jié)不必太過關(guān)注,甚至沒弄明白也沒有關(guān)系,你只要理解這些擴展點是在何時如何工作的,能讓它們?yōu)槟闼眉纯伞?/span>
            整個啟動流程確實非常復雜,可以查詢參考資料中的部分章節(jié)和內(nèi)容,對照著源碼,多看看,我想最終你都能弄清楚的。言而總之,Spring才是核心,理解清楚Spring容器的啟動流程,那Spring Boot啟動流程就不在話下了。
            如有文章對你有幫助,在看”和轉(zhuǎn)發(fā)是對我最大的支持!


            精通 Spring Boot 系列文(1)
            精通 Spring Boot 系列文(2)
            精通 Spring Boot 系列文(2)
            精通 Spring Boot 系列文(4)
            精通 Spring Boot 系列文(5)
            精通 Spring Boot 系列文(6)
            精通 Spring Boot 系列文(7)
            精通 Spring Boot 系列文(8)
            精通 Spring Boot 系列文(9)
            精通 Spring Boot 系列文(10)
            精通 Spring Boot 系列文(11)
            精通 Spring Boot 系列文(12)

            Java后端編程

            更多Java推文,關(guān)注公眾號

            瀏覽 28
            點贊
            評論
            收藏
            分享

            手機掃一掃分享

            分享
            舉報
            評論
            圖片
            表情
            推薦
            點贊
            評論
            收藏
            分享

            手機掃一掃分享

            分享
            舉報
            1. <strong id="7actg"></strong>
            2. <table id="7actg"></table>

            3. <address id="7actg"></address>
              <address id="7actg"></address>
              1. <object id="7actg"><tt id="7actg"></tt></object>
                成人毛片一区二区三区| 99久久婷婷国产综合精品hsex,亚 91小宝寻花一区二区三区三级 | 无码伊人| 另类图片亚洲色图| 99青草在线视频| 日韩av中文在线| 国产拍拍视频| 色婷婷AV在线观看| 91理论片| 凹凸熟女凹凸BBWBBW| 亚洲电影中文字幕| 17c精品麻豆一区二区免费| 小處女末发育嫩苞AV| 伊人网成人| 国产69精品久久久久久久久久久久 | 成人婷婷五月| 国产91久久婷婷一区二区| 欧美性少妇| 国产无遮挡又黄又爽免费网站| 欧美美女日逼视频| 国产午夜精品一区二区三区四区| 一级黄色网| 波多野结衣网址| av黄色网| 操学生妹| 久久丝袜| 久色入口| 思思热思思操| 久久精品视频18| XXXX操| 五月丁香欧美综合| h网站在线| 日韩精品成人专区无码| 无码精品在线观看| 中文字幕av久久爽Av| 久久一区二区三区四区| 婷婷五月伊人| 欧美黄片免费在线观看| 久久国产精品视频| 搡BBB搡BBBB搡BBBB'| 国产中文在线观看| 黄网站在线观看| 伊人色综合网| 亚洲AV秘无码苍井空| 蜜臀久久99精品久久久久久牛牛| AAA片视频| 激情深爱| 国产无码一| 亚洲欧洲日韩综合| 爱爱91| 玖热精品| 亚洲福利在线观看视频| 夜夜爱爱| 91麻豆精品国产91久久久久久 | 中国黄色学生妹一级片| 日韩综合在线| 好男人一区二区三区在线观看| 福利网址| 夜色福利视频| 亚洲日韩AV在线| 俺来也影院| 激情播播网| 中文日韩字幕| 在线观看高清无码| 影音先锋婷婷| yw在线播放| 特黄AV| 99热9| 国产久久久久久久久久| 日韩黄片视频| 妹子色综合| 91国语对白| 日韩免费福利视频| 青春草在线免费视频| 一道本在线视频| 日本爱爱片| 天堂成人在线| 特黄A级毛片| 成人高清无码视频| 精品一区二区三区蜜桃臀www | 欧美日本中文字幕| 91丝袜一区二区| 欧美在线视频网| 无码人妻一区二区| 成人精品一区日本无码网站suv/ | 在线观看视频免费无码免费视频 | 亚洲日韩在线a成| 国际精品久久久| 男人的天堂色婷婷| 球AV在线| 精品亚洲一区二区三区四区五区| 影音先锋aV成人无码电影| 欧美MV日韩MV国产网站| 久久99热这里只频精品6学生| 日逼高清视频| 波多野结衣成人在线| 亚洲成人视频一区二区| 久久艹视频| 超碰观看| 军人妓女院BD高清片在线播放| 成人久久大香蕉| 婷婷色色婷婷| 欧美日韩一区二区三区四区五区六区| 大香蕉精品在线视频| 亚洲国产视频在线观看| 开心深爱激情网| 老妇bbw| 大鸡吧网| 国产高清无码网站| 免费看黃色AAAAAA片| 成人视频黄片| 亚洲日韩成人电影| 操B网站| 久久九九电影| 日韩视频一区二区三区| 亚洲专区区免费| 7777影视电视剧在线观看官网 | 黄片网站入口| 久久久久伊人| 99A片| 十八毛片| 亚州操B| 粉嫩av一区二区白浆| 99精品久久久久久无码| 影音先锋色资源站| 欧美亚洲| 国产亚洲欧美视频| 免费18蜜桃久久19| 成人视频一区| 熟女视频一区二区| 国产一区在线视频| 亚洲日韩在线中文字幕| 欧美一在线一综合| 欧美在线小视频| 91人人妻人人妻人人澡| 亚洲天堂在线视频观看| 国产乱伦中文字幕| 黄色免费在线观看网站| 欧美日韩网站| 99精品视频免费在线观看| 51黄片| 超碰在线网站| 又大又粗AV| 大香蕉福利视频| 国产精品污www在线观看| av天天日| 99视频在线播放| 亚洲成人精品一区二区| 青青热视频| 久久国产成人| 97精品人妻一区二区| 午夜无码在线观看视频| 99热99精品| 在线视频A| 国产狂喷水潮免费网站www| 熟妇人妻中文AV无码| 久久综合无码内射国产| 久久国产大奶| 天天干天天色天天日| 黄频视频| 国产成人精品一区二区| 毛片毛片毛片毛片毛片| 亚洲日本无码50p| 亚洲综合伊人| 91九色91蝌蚪91窝成人| 一区二区经典| 一区二区Av| 四川少扫搡BBBBB搡B| 东北骚妇大战黑人视频| 亚洲黄色免费网站| 亚洲色鬼| 久久久久久国产免费A片| 日韩精品电影| 久久久91人妻无码精品蜜桃ID| 日韩欧美久久| 日韩欧美第一页| 黄网91| 亚洲手机在线| 大香蕉老师| 未满十八18禁止免费无码网站| 在线观看国产视频| 久久艹国产| 黄色在线视频观看| 欧美特黄AAAAAA| 欧美成人视频18| 三级片无码视频| 最新中文字幕av| 久久久久久久久久久久国产精品 | 中文一区| 婷婷午夜福利| aaa精品视频| 成人aV免费观看| 一级女婬片A片AAAA片| 大香蕉国产精品视频| 黄色电影视频在线| 久久久久国产精品视频| 黄色激情网站| 亚洲AV无码成人精品区国产| 国内精品久久久久久久| 成人免费在线电影| 8x8拨牐拨牐拨牐永久免费| 999日本不卡影院| 国产乱伦电影| 欧美黄色免费观看| 色综合加勒比| 成人无码91| 伊人网在线| 俺来也影院| 欧美三级网站| JUY-579被丈夫的上司侵犯后的第7天,我 | 日本女优婷婷青青草原| 北京熟妇槡BBBB槡BBBB| 18禁av在线| 夜夜干天天操| 久草视频在线免费播放| 在线免费高清无码| 亚洲色逼图片| 最新中文字幕在线观看| 成人网站在线免费看| 亚洲人妻在线观看| 国产欧美日韩一区二区三区| 人人综合网| 天堂精品在线| 日韩在线视频网站| 一级Aa视频免费看| 日本韩国欧美18| 五月丁香激情四射| 日韩无码破解| 九九热精品视频| 日本A级视频| 操逼网站大全| 亚洲av电影网| 亚洲综合中文字幕在线| 少妇探花| 中文字幕特黄A片| 男女视频网站| 爆乳尤物一区二区三区| 国产一卡二卡在线| 凹凸熟女凹凸BBWBBW| 一道本一区二区三区| 一级中国毛片| 日本久久综合| 亚洲AV无码精品久久一区二区| 国产一级婬乱A片| 懂色一区二区二区在线播放视频| 高清无码免费| 丁香五月欧美| 蜜桃av在线播放| 骚五月| 曰曰摸日日碰| 亚洲综合在线观看视频| 无码人妻在线播放| 久久9视频| 欧美日韩字幕| 国产热| 99国产免费视频| 婷婷五月天色综合| 精品成人在线观看| 日韩無码专区| 日韩中文字幕专区| 亚洲精品麻豆| 欧美v在线观看| 天天操人人射| 天天爱天天插| 成人TV| 69av网站| 波多野结衣av在线观看| 亚欧一区二区| 超碰97老师| 中文字幕第72页| 国产激情在线| 久草资源在线观看| 乌克兰性爱视频| 北条麻妃性爱视频| 婷色五月天| 天天日天天日天天干| 婷婷久久网| 日本欧美黄色| 在线成人免费视频| 老司机av| 久久精品小视频| 精产国品一区二区区别| av无码在线观看| 91三级片| 小泽玛利亚一区二区免费| 2014亚洲天堂| 中国一级黄片| www.啪| 国产一级影院| 五月天色色图| 四虎成人在线| 春色AV| 最新无码在线| 中文AV字幕| 免费在线成人网站| 欧美色图在线视频| 蜜桃av| 色色色成人视频| 欧美熟妇性爱视频| 亚洲AV永久无码国产精品久久| 538在线观看| 香蕉在线观看| 91视频在线观看免费大全| 人人操91| 国产无码做爱视频| 亚洲黄色视频在线观看网站| 欧美成人三级在线播放| 日逼中文字幕| 成人区精品一区二区婷婷| 日韩中文字幕AV| 国产一区二区三区四区视频| 欧美撒色逼撒| 噜噜噜网| 欧美影院亚洲| 久久婷婷在线| 精品国产成人a在线观看| 黄片久久久| 天堂网在线视频| 久久精品大屁股| 日本一区二区三区四区| 久久久久亚洲AV无码专区成人| 久久免费视频网站| 俺去俺来也在线www色情网 | 亚洲AV免费看| 亚洲www啪成人一区二区麻豆| 在线观看亚洲| 日韩一区二区免费看| 黄色综合网站| 91精品日韩| 99热99在线| 中文字幕免费在线| 熟女人妻一区二区三区| 亚洲人成77777| 无码人妻一区二区三区蜜桃视频| 中文字幕区| 大香伊人中文字幕精品| 欧美三P囗交做爰| 国产欧美日韩综合| 国产精品7777| 亚洲v区| 欧美污网站| 丁香六月| 欧美爱爱免费看| 成人无码91| 亚洲人天堂| 91久久国产性奴调教| 久久久精品| 久久亚洲AV无码午夜麻豆| 欧美日韩一道本| 三级片一区二区| 日日爽夜夜爽| 天堂无码视频在线播放| 黄色小说视频| 天堂在线中文字幕| 亚洲一级二级| 色欲一区二区三区| 中文字幕第11页| 国产A片免费| 999精品视频| 北条麻妃A片在线播放| 午夜三区| 亚洲精品成人在线| 天天干天天干| 2021国产精品视频| 人妻无码一区二区| 九色91| 欧美精产国品一| 99国产在线观看免费视频| 人妻人人澡| 国产香蕉视频| 美女操b| 狠操在线| 黄片网站在线观看| 中文无码AV在线| 99日韩| 在线观看无码视频| 国产精品一区二区在线播放| 国产激情在线观看视频| 中文字幕视频在线播放| 国产日韩欧美综合精品在线观看 | 人人人操人人| 国产成人激情视频| 国产精品99久久久久的广告情况| 欧美人人插| 国产噜噜噜噜久久久久久久久| 全国最大成人网| 91人妻日韩人妻无码专区精品 | 国产a一级a毛一级视频| 美女被操面费网站| 特级黄色视频| 欧美成人A级片| 麻豆AV免费看| 亚洲无码专区在线| 亚洲三级国产| 国产免费一级片| 五月丁香在线视频| 国产精品宾馆| 97成人人妻一区二区三区| 久久免费看视频| 国产精品污www在线观看| 日本人妻视频| 99激情网| 日韩免费高清无码| 欧美一级AAA大片免费观看| 黄片一区二区三区| 毛片自拍| 日韩一级无码特黄AAA片| 亚洲中文视频免费| 婷婷性爱五月天| 欧美色图15p| 97国产在线观看| 男女性爱视频免费| 蜜桃AV一区二区三区| 中文字幕99页| 精品无码一区二区三区四区久久久软件 | 精品人妻在线| 18禁日韩| 免费在线观看内射| 四虎欧美| 国产精品黑人ThePorn| 97免费| 黄色亚洲无码| 日本中文字幕不卡| 激情无码五月天| 中文在线字幕免费观看| 无码免费高清| 欧美在线视频你懂的| 男女做爱视频网站| 999精品视频在线| 成人在线一区二区三区| 怡红院av| av一卡二卡| 五月婷婷激情网| 久久久91人妻无码精品蜜桃ID| 91狠狠综合久久久久久| 日韩一区二区在线看在线看 | 人妻综合第一页| 黄色特级片| 午夜老司机福利一二三区| 欧美三级视频在线观看| 国精产品一二三区| 免费黄色av| 丁香乱伦| 人人操碰成人网| 9999国产精品| 安徽少妇搡bbw搡bbbb| www.婷婷六月天| 大香蕉视频国产| www.蜜桃视频| 欧美成人性爱影院| 欧洲一区二区| 亚洲日韩精品无码| 丁香五月综合| 中文字幕成人A片| 亚洲人妻性爱| 色欲一区| 97精品久久| 日韩城人网站| 日日操夜夜爽| 最美人妖系列国产Ts涵涵| 丁香五月色| 无码乱| 丁香婷婷男人天堂| 91啦丨露脸丨熟女色啦| 国产又粗又大又黄视频| 黄色一级录像| 粉嫩小泬BBBBBB免费看| 国产換妻4P视频| 国产免费黄色片| 午夜亚洲AⅤ无码高潮片苍井空 | 黄色视频网站在线| 欧美射精视频| 91丨PORNY丨在线中文| 国产精品a片| 婷婷成人视频| 三级一区二区| 92丨九色丨偷拍老熟女| 97午夜福利视频| 玩弄大乳乳妾高潮乳喷视频| 99在线精品视频免费观看20| 日韩国产免费| 男人天堂成人| 天天日天天射天天干| 黄色视频免费在线观看网站 | 视频一区中文字幕| 久久一卡二卡| 国语偷拍| 国产亚洲欧美一区二区| 亚洲AV无码一区| 亚洲AV秘成人久久无码海归| 久久精品操| 操东北老女人| 日本黄色视| 亚洲成人无码视频在线观看| 久久无码成人| 爱爱91| jk在线观看| 欧美不卡一区二区| 天天综合字幕一区二区| 亚洲人妻中文字幕| 无码白浆| 一插菊花综合视频| 狠狠色噜噜狠狠狠7777米奇网| 天天无码| 日韩无码二区| 台湾精品无码| 动漫3D成人H无码国漫| 女BBBBBB女BBB| 成人性爱在线观看| 精品一区二区久久久久久久网站| 色哟哟一中文字慕| 久久亚洲AV无码午夜麻豆| 久久国产大奶| 在线一区二区三区四区| jk在线观看| 无码人妻精品一区二区蜜桃漫画 | 丁香六月综合激情| 操逼人妻| 97黄色| 无码视频在线播放| 国产精品扒开腿做爽爽爽A片唱戏| 精品欧美视频| 国产丰满乱子伦无码| 色呦呦视频| 国产6区| 亚洲成人AV| 超碰97人人操| 色综合天天操| 亚洲国产综合AV在线| 搞AV网| 亚洲最大视频| 欧美日韩免费在线| 在线观看无码高清视频| 在线观看成人三级片| 性99网站| 97色综合| 国产1区2区| 在线少妇| 日韩精品一区二区在线观看| 亚洲最新无码| 中国AV网| 91艹艹| 国产69av| 日本一节片在线播放| 99综合网| 日韩无| 成人无码电影在线观看| 91丨九色丨熟女泻火| 99久久性爱| 亚洲免费观看高清完整| 熟女人妻一区二区三区免费看| 牛牛成人在线视频| 日韩AV在线免费观看| 国产精品成人无码免费| 免费网站观看www在线观看| 亚洲精品一区二区三区新线路| www.18av| 亚洲精品久久久久毛片A级绿茶| 精品一区二区三区四区五区六区七区八区九区 | 91麻豆精品传媒国产| 久久黑人| 手机毛片| 欧美成人免费电影| 超碰免费观看| 江苏妇搡BBB搡BBBB| 国产精品揄拍100视频| 欧美三级理论片| 激情无码一区二区三区| 日韩毛片在线看| 丰满人妻一区二区三区四区不卡| 欧美MV日韩MV国产网站| 精品久久久无码| 日本高清视频网站网wwwwww| 強姦婬片A片AAA毛片Mⅴ| 人人妻人人操人人| 亚洲欧洲AV| 中文字幕区| 九九精品99| 俺也日| 狠狠干狠狠爱| 粗长哭叫打桩H体育生| 激情五月激情综合网| 国产深喉视频| 欧美激情四射| 麻豆自拍偷拍视频| 俺去俺来也在线www色情网| 色播国产成人AV| 在线观看av资源| 婷婷丁香色| 啊啊啊啊啊在线观看| 亚洲精品秘一区二区三区影| 免费观看一级毛一片| 国产青草| 开心激情网五月天| 久热视频在线| 一区二区三区四区五区无码| 波多野结衣一级婬片A片免费下载| BBB搡BBB搡BBB搡BBB| 美女极度色诱图片www视频| 家庭乱伦AV| 久操福利视频| 玖玖成人电影| 国产在线拍揄自揄拍无码网站新闻 | 中文字幕AⅤ在线| 综合在线视频| 色999| 欧美自拍偷拍| 国产色情性黄片Av网站| 蜜桃91精品秘入口内裤| 99成人乱码一区二区三区在线| 91亚洲国产成人| 国产精品欧美综合亚洲| 国产欧美精品AAAAAA片| 亚洲免费观看在线观看| 日B免费视频| 丁香五月亚洲| 一级片av| 国产成人免费观看| 精品国产久久| 中文字幕在线观看一区| yOujiZZ欧美精品| 免费黄色大片网站| 调教人妻视频| AV手机天堂| www.91熊猫成人网| 国产黄色免费电影| 亚洲人成无码| 97人人妻| 国产在线不卡年轻点的| 亚洲成人电影AV| 屁屁影院CCYYCOM发布地| 精品久久免费视频| 久久久夜夜夜| 日本黄色小视频| 日韩免费视频在线观看| 丰满人妻一区二区免费看| 未满十八18禁止免费无码网站| 色婷久久| 天天色天天色天天色| 日本a级视频| 一区二区三区久久久| 黄网| 亚洲AV成人片色在线观看高潮 | 福利毛片| 乱子伦国产精品| 国产成人超碰| 亚洲人人爱| 日韩免费a| 一级一级一级做a免费一级做a| 苍井空一区二区| 欧美成人视频。| 人人操91| 人人爱人人摸人人操| 无码四区| 久久无码电影| 成人黄色在线看| 青青在线免费视频| 黄色视频亚洲| 韩国AV三级| a免费视频在线观看| AV2014天堂网| 免费亲子乱婬一级A片| 露脸偷拍AV2025| 欧美成人性爱影院| 啪一啪操一操| 东京热AV在线| 91麻豆福利在线观看| 人妻熟女在线视频| 日本欧美在线播放中文| 99久久久久久久| 中文字幕av高清片,中文在线观看 www一个人免费观看视频www | 天天视频入口| 国产亚洲久一区二区三区| 91香蕉国产| 一级操逼视频免费观看| 91在线免费播放| 成人免费看片| 国产激情无码| 六十路老熟女码视频| 国产无遮挡| 久精品视频| 好看的中文字幕av| 天天干天天日天天射| 狠狠干在线视频| 中文字幕日韩无码片| 狠狠狠狠狠狠狠狠| 操逼大香蕉| 亚洲国产成人自拍| 91香蕉国产视频| 91资源在线| 欧美在线观看一区二区| 中文字幕日本无码| 毛片福利| 人妻日日| 中文字幕在线国产| 成人免费观看的毛视频| 国产91无码精品秘入口| 国产精品视频免费看| 国产高清做爱免费在线视频| 日韩a在线观看| 久草福利在线视频| 开心激情播播网| 中文字幕亚洲无码视频| 内射少妇18| 国产精品免费一区二区三区四区视频 | www.wuma| 亚洲欧美日韩高清| 在线高清无码| 91极品视觉盛宴| 久久精品视频18| 九九综合精品| 97精品在线视频| 操逼视频一区| 免费成人视频| 强伦轩一区二区三区四区播放方式 | 亚洲综合网在线| A一级黄色片| 东方美美高清无码一区| 爆操网站| 亚洲一区视频| 在桌下含她的花蒂和舌头H视频| 午夜无码AV| 国产成人免费观看视频| 日韩在线中文字幕视频| 91人妻人人澡人人爽人人精吕| 天天夜夜操| 青青在线| 国产91视频在线观看| 波多野结衣无码在线| 大香蕉伊人综合在线| 高清无码视频在线播放| 精品人妻无码一区二区三区| 亚洲无码电影网站| 久久午夜无码鲁丝午夜精品| 亚洲福利在线观看视频| 蜜桃视频91| 免费A网站| 日本一级片中文字幕| 久久精品女人| 欧美成人乱码一区二区三区| 性爱福利视频| 国产欧美在线免费观看| 亚洲无码999| 国产特黄级AAAAA片免| 最近中文字幕| 91丨熟女丨露脸| 国产精品国产精品国产专区不片| 五月天啪啪视频| 都市激情亚洲| 北条麻妃99精品青青久久| www.av在线| 操操操无码| 黑人av| 欧美操逼网址| 亚洲日韩中文无码| 天天干天天日天天色| 亚洲色成人网站www永久四虎| 91成人免费电影| yw尤物在线| 四虎在线观看| 丁香五月亚洲| 亚洲天堂天天| 国产无套内射在线观看| 无码专区av| 黄色小视频在线观看| 看一级黄色视频| 少妇在线观看| 91丨九色丨熟女新版| www.伊人大香蕉| 97人妻一区二区三区| 亚洲成人一二三区| 99激情| 婷婷久久婷婷| 大香蕉伊人影视| 一本色道久久综合熟妇| 国内自拍视频网站| 大香蕉伊人网| 91日韩精品| 中文字幕乱在线| 国内精品无码| 国产精品高清无码| 手机AV免费| 一区二区三区四区无码视频| 国产亚洲99久久精品熟女| 午夜操逼| 岛国免费AV| jlzz18| 亚洲AV无码成人精品区天堂小说 | 江苏妇搡BBBB搡BBBB-百度| 亚洲成人影片| 免费看成人A片无码照片88hⅤ| 亚洲成人在线视频免费观看| 色久综合| 午夜性爱AV| 国产欧美日韩一区| 精品一二三区| 国产操美女| 黄色一级大片在线免费看产| 久久婷婷五月| 成人免费在线观看| 无码视频久久| 自拍一区在线观看| 91人人爽| 亚洲毛片在线| 搡BBB搡BBBB搡BBBB'| 亚洲精品乱码久久久久久按摩观| 色色国产| 蜜臀久久久99久久久久久久| 欧美日韩高清一区| 欧美一区二区在线| 欧美肉大捧一进一出小说| 波多野吉衣av| 精品黄色毛片| 久久亚洲天堂| 亚洲黄色一区| 中国a一片一级一片| 精品无码一区二区人妻久久蜜桃 | 日韩一级黄色电影| 精品视频国产| 91视频在线网站| 国产成人V在线精品一区| 大香蕉黄色电影| 91日逼视频| 色综合欧美| 超碰在线图片| 日韩在线视频不卡| 超碰人人干| 免费视频一二三区| 日韩三级一区| 高潮毛片| 精品无码二区| 免费一区二区三区四区| a网站在线观看| 伦理被部长侵犯HD中字| 男人天堂视频在线| 亚洲视频三区| 黄色无無| 99在线免费观看| 老熟女一区二区三区| 国产精品无码免费视频| 久久青娱乐| 无码日韩成人| 殴美亚洲一流| 大香蕉伊人在线观看| 中文字幕+乱码+中文乱码电影| 2021天天操| 精品色播| 欧美十区| 大香蕉伊人成人网| 日韩一级黄色电影| 欧美不卡一区| 淫五月| caopeng97| 少妇厨房愉情理伦BD在线观看| 在线国产激情视频| 99久久综合国产精品二区| 四虎国产精品成人久久| 天天色色| 国产精品久久久久毛片SUV| 亚洲视频中文| 免费看18禁| 亚洲精品不卡| 99黄片| 性爱日韩| 麻豆亚洲| 日韩在线中文字幕| 久久青青草在线视频| 久久婷婷无码视频| 欧美性猛交一区二区三区精品| 无码人妻一区二区三区四区老鸭窝| 日韩免费高清无码| 午夜无码精品一区二区三区99午 | 成人啪啪视频| 超碰精品在线| 日本欧美亚洲| 免费操| 美女乱伦| 无码中文字幕在线播放| 色综合激情| 欧美高清无码在线观看| 四虎麻豆| 在线免费观看网站| 无码欧美成人AAAA三区在线| 丰满熟妇人妻无码视频| 91精品人妻一区二区三区四区| 国产无遮挡又黄又爽又色视频软件 | 日逼网站免费观看| 无码一道本一区二区无码| AV中文在线观看| 授乳奶水x88MAV| 午夜激情四射| 无码白浆| 久久精品视频99| AV在线资源网| 特级西西444WWW视频| 丁香五月天啪啪| 成人国产AV精| 亚洲无码AV在线播放| 夜夜骑夜夜| 怡红院视频|