日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

springboot中接口实例化_疫情爆发在家闲出屁的我,梳理一下SpringBoot知识点

發(fā)布時(shí)間:2025/1/21 javascript 111 豆豆
生活随笔 收集整理的這篇文章主要介紹了 springboot中接口实例化_疫情爆发在家闲出屁的我,梳理一下SpringBoot知识点 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

在過(guò)去兩三年的Spring生態(tài)圈,最讓人興奮的莫過(guò)于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)物,它能夠幫助開(kāi)發(fā)者更快速高效地構(gòu)建基于Spring生態(tài)圈的應(yīng)用。
那Spring Boot有何魔法?自動(dòng)配置、起步依賴、Actuator、命令行界面(CLI) 是Spring Boot最重要的4大核心特性,其中CLI是Spring Boot的可選特性,雖然它功能強(qiáng)大,但也引入了一套不太常規(guī)的開(kāi)發(fā)模型,因而這個(gè)系列的文章僅關(guān)注其它3種特性。如文章標(biāo)題,本文是這個(gè)系列的第一部分,將為你打開(kāi)Spring Boot的大門,重點(diǎn)為你剖析其啟動(dòng)流程以及自動(dòng)配置實(shí)現(xiàn)原理。要掌握這部分核心內(nèi)容,理解一些Spring框架的基礎(chǔ)知識(shí),將會(huì)讓你事半功倍。
一、拋磚引玉:探索Spring IoC容器
如果有看過(guò) SpringApplication.run()方法的源碼,Spring Boot冗長(zhǎng)無(wú)比的啟動(dòng)流程一定會(huì)讓你抓狂,透過(guò)現(xiàn)象看本質(zhì),SpringApplication只是將一個(gè)典型的Spring應(yīng)用的啟動(dòng)流程進(jìn)行了擴(kuò)展,因此,透徹理解Spring容器是打開(kāi)Spring Boot大門的一把鑰匙。
1.1、Spring IoC容器
可以把Spring IoC容器比作一間餐館,當(dāng)你來(lái)到餐館,通常會(huì)直接招呼服務(wù)員:點(diǎn)菜!至于菜的原料是什么?如何用原料把菜做出來(lái)?可能你根本就不關(guān)心。IoC容器也是一樣,你只需要告訴它需要某個(gè)bean,它就把對(duì)應(yīng)的實(shí)例(instance)扔給你,至于這個(gè)bean是否依賴其他組件,怎樣完成它的初始化,根本就不需要你關(guān)心。
作為餐館,想要做出菜肴,得知道菜的原料和菜譜,同樣地,IoC容器想要管理各個(gè)業(yè)務(wù)對(duì)象以及它們之間的依賴關(guān)系,需要通過(guò)某種途徑來(lái)記錄和管理這些信息。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ì)通過(guò)這些信息為客戶端返回一個(gè)完整可用的bean實(shí)例。
原材料已經(jīng)準(zhǔn)備好(把BeanDefinition看著原料),開(kāi)始做菜吧,等等,你還需要一份菜譜, BeanDefinitionRegistry和 BeanFactory就是這份菜譜,BeanDefinitionRegistry抽象出bean的注冊(cè)邏輯,而BeanFactory則抽象出了bean的管理邏輯,而各個(gè)BeanFactory的實(shí)現(xiàn)類就具體承擔(dān)了bean的注冊(cè)以及管理工作。它們之間的關(guān)系就如下圖:


BeanFactory、BeanDefinitionRegistry關(guān)系圖(來(lái)自: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的方法。
下面通過(guò)一段簡(jiǎn)單的代碼來(lái)模擬BeanFactory底層是如何工作的:
// 默認(rèn)容器實(shí)現(xiàn)DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();// 根據(jù)業(yè)務(wù)對(duì)象構(gòu)造相應(yīng)的BeanDefinitionAbstractBeanDefinition definition = new RootBeanDefinition(Business.class,true);// 將bean定義注冊(cè)到容器中beanRegistry.registerBeanDefinition("beanName",definition);// 如果有多個(gè)bean,還可以指定各個(gè)bean之間的依賴關(guān)系// ........// 然后可以從容器中獲取這個(gè)bean的實(shí)例// 注意:這里的beanRegistry其實(shí)實(shí)現(xiàn)了BeanFactory接口,所以可以強(qiáng)轉(zhuǎn),// 單純的BeanDefinitionRegistry是無(wú)法強(qiáng)制轉(zhuǎn)換到BeanFactory類型的BeanFactory container = (BeanFactory)beanRegistry;Business business = (Business)container.getBean("beanName");
這段代碼僅為了說(shuō)明BeanFactory底層的大致工作流程,實(shí)際情況會(huì)更加復(fù)雜,比如bean之間的依賴關(guān)系可能定義在外部配置文件(XML/Properties)中、也可能是注解方式。Spring IoC容器的整個(gè)工作流程大致可以分為兩個(gè)階段:
①、容器啟動(dòng)階段
容器啟動(dòng)時(shí),會(huì)通過(guò)某種途徑加載 ConfigurationMetaData。除了代碼方式比較直接外,在大部分情況下,容器需要依賴某些工具類,比如:BeanDefinitionReader,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)證性或者輔助性的工作也在這一階段完成。
來(lái)看一個(gè)簡(jiǎn)單的例子吧,過(guò)往,所有的bean都定義在XML配置文件中,下面的代碼將模擬BeanFactory如何從配置文件中加載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)過(guò)第一階段,所有bean定義都通過(guò)BeanDefinition的方式注冊(cè)到BeanDefinitionRegistry中,當(dāng)某個(gè)請(qǐng)求通過(guò)容器的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í)例化完成。如果沒(méi)有,則會(huì)根據(jù)注冊(cè)的BeanDefinition所提供的信息實(shí)例化被請(qǐng)求對(duì)象,并為其注入依賴。當(dāng)該對(duì)象裝配完畢后,容器會(huì)立即將其返回給請(qǐng)求方法使用。
BeanFactory只是Spring IoC容器的一種實(shí)現(xiàn),如果沒(méi)有特殊指定,它采用采用延遲初始化策略:只有當(dāng)訪問(wèn)容器中的某個(gè)對(duì)象時(shí),才對(duì)該對(duì)象進(jìn)行初始化和依賴注入操作。而在實(shí)際場(chǎng)景下,我們更多的使用另外一種類型的容器:ApplicationContext,它構(gòu)建在BeanFactory之上,屬于更高級(jí)的容器,除了具有BeanFactory的所有能力之外,還提供對(duì)事件監(jiān)聽(tīng)機(jī)制以及國(guó)際化的支持等。它管理的bean,在容器啟動(dòng)時(shí)全部完成初始化和依賴注入操作。
1.2、Spring容器擴(kuò)展機(jī)制
IoC容器負(fù)責(zé)管理容器中所有bean的生命周期,而在bean生命周期的不同階段,Spring提供了不同的擴(kuò)展點(diǎn)來(lái)改變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來(lái)說(shuō)明其大致的工作流程。
在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配置文件中的值來(lái)替換相應(yīng)的BeanDefinition中占位符所表示的屬性值。當(dāng)需要實(shí)例化bean時(shí),bean定義中的屬性值就已經(jīng)被替換成我們配置的值。當(dāng)然其實(shí)現(xiàn)比上面描述的要復(fù)雜一些,這里僅說(shuō)明其大致工作原理,更詳細(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è)接口:
public interface BeanPostProcessor { // 前置處理 Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; // 后置處理 Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;}
為了理解這兩個(gè)方法執(zhí)行的時(shí)機(jī),簡(jiǎn)單的了解下bean的整個(gè)生命周期:


Bean的實(shí)例化過(guò)程(來(lái)自:Spring揭秘)
postProcessBeforeInitialization()方法與 postProcessAfterInitialization()分別對(duì)應(yīng)圖中前置處理和后置處理兩個(gè)步驟將執(zhí)行的方法。這兩個(gè)方法中都傳入了bean對(duì)象實(shí)例的引用,為擴(kuò)展容器的對(duì)象實(shí)例化過(guò)程提供了很大便利,在這兒幾乎可以對(duì)傳入的實(shí)例執(zhí)行任何操作。注解、AOP等功能的實(shí)現(xiàn)均大量使用了 BeanPostProcessor,比如有一個(gè)自定義注解,你完全可以實(shí)現(xiàn)BeanPostProcessor的接口,在其中判斷bean對(duì)象的腦袋上是否有該注解,如果有,你可以對(duì)這個(gè)bean實(shí)例執(zhí)行任何操作,想想是不是非常的簡(jiǎn)單?
再來(lái)看一個(gè)更常見(jiàn)的例子,在Spring中經(jīng)常能夠看到各種各樣的Aware接口,其作用就是在對(duì)象實(shí)例化完成以后將Aware接口定義中規(guī)定的依賴注入到當(dāng)前實(shí)例中。比如最常見(jiàn)的 ApplicationContextAware接口,實(shí)現(xiàn)了這個(gè)接口的類都可以獲取到一個(gè)ApplicationContext對(duì)象。當(dāng)容器中每個(gè)對(duì)象的實(shí)例化過(guò)程走到BeanPostProcessor前置處理這一步時(shí),容器會(huì)檢測(cè)到之前注冊(cè)到容器的ApplicationContextAwareProcessor,然后就會(huì)調(diào)用其postProcessBeforeInitialization()方法,檢查并設(shè)置Aware相關(guān)依賴。看看代碼吧,是不是很簡(jiǎn)單:
// 代碼來(lái)自: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的啟動(dòng)原理,如果在后續(xù)的學(xué)習(xí)過(guò)程中遇到一些晦澀難懂的知識(shí),再回過(guò)頭來(lái)看看Spring的核心知識(shí),也許有意想不到的效果。也許Spring Boot的中文資料很少,但Spring的中文資料和書籍有太多太多,總有東西能給你啟發(fā)。
二、夯實(shí)基礎(chǔ):JavaConfig與常見(jiàn)Annotation
2.1、JavaConfig
我們知道 bean是Spring IOC中非常核心的概念,Spring容器負(fù)責(zé)bean的生命周期的管理。在最初,Spring使用XML配置文件的方式來(lái)描述bean的定義以及相互間的依賴關(guān)系,但隨著Spring的發(fā)展,越來(lái)越多的人對(duì)這種方式表示不滿,因?yàn)镾pring項(xiàng)目的所有業(yè)務(wù)類均以bean的形式配置在XML文件中,造成了大量的XML文件,使項(xiàng)目變得復(fù)雜且難以管理。
后來(lái),基于純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注解來(lái)描述bean之間的依賴綁定關(guān)系。比如,下面是使用XML配置方式來(lái)描述bean的定義:
<bean id="bookService" class="cn.moondev.service.BookServiceImpl"></bean>
而基于JavaConfig的配置形式是這樣的:
@Configurationpublic class MoonBookConfiguration { // 任何標(biāo)志了@Bean的方法,其返回值將作為一個(gè)bean注冊(cè)到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中則是這樣:
@Configurationpublic class MoonBookConfiguration { // 如果一個(gè)bean依賴另一個(gè)bean,則直接調(diào)用對(duì)應(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(); }}
你可能注意到這個(gè)示例中,有兩個(gè)bean都依賴于dependencyService,也就是說(shuō)當(dāng)初始化bookService時(shí)會(huì)調(diào)用 dependencyService(),在初始化otherService時(shí)也會(huì)調(diào)用 dependencyService(),那么問(wèn)題來(lái)了?這時(shí)候IOC容器中是有一個(gè)dependencyService實(shí)例還是兩個(gè)?這個(gè)問(wèn)題留著大家思考吧,這里不再贅述。
2.2、@ComponentScan
@ComponentScan注解對(duì)應(yīng)XML配置形式中的 <context:component-scan>元素,表示啟用組件掃描,Spring會(huì)自動(dòng)掃描所有通過(guò)注解配置的bean,然后將其注冊(cè)到IOC容器中。我們可以通過(guò) basePackages等屬性來(lái)指定 @ComponentScan自動(dòng)掃描的范圍,如果不指定,默認(rèn)從聲明 @ComponentScan所在類的 package進(jìn)行掃描。正因?yàn)槿绱?#xff0c;SpringBoot的啟動(dòng)類都默認(rèn)在 src/main/java下。
2.3、@Import
@Import注解用于導(dǎo)入配置類,舉個(gè)簡(jiǎn)單的例子:
@Configurationpublic class MoonBookConfiguration { @Bean public BookService bookService() { return new BookServiceImpl(); }}
現(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的定義注冊(cè)到IOC容器中。
2.4、@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í)才生效:
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來(lái)聲明bean的時(shí)候,可以使用這個(gè)自定義條件類:
@Conditional(JdbcTemplateCondition.class)@Servicepublic MyService service() { ......}
這個(gè)例子中只有當(dāng) JdbcTemplateCondition類的條件成立時(shí)才會(huì)創(chuàng)建MyService這個(gè)bean。也就是說(shuō)MyService這bean的創(chuàng)建條件是 classpath里面包含 JdbcTemplate,否則這個(gè)bean的聲明就會(huì)被忽略掉。
SpringBoot定義了很多有趣的條件,并把他們運(yùn)用到了配置類上,這些配置類構(gòu)成了 SpringBoot的自動(dòng)配置的基礎(chǔ)。SpringBoot運(yùn)用條件化配置的方法是:定義多個(gè)特殊的條件化注解,并將它們用到配置類上。下面列出了 SpringBoot提供的部分條件化注解:
條件化注解配置生效條件@ConditionalOnBean配置了某個(gè)特定bean@ConditionalOnMissingBean沒(méi)有配置特定的bean@ConditionalOnClassClasspath里有指定的類@ConditionalOnMissingClassClasspath里沒(méi)有指定的類@ConditionalOnExpression給定的Spring Expression Language表達(dá)式計(jì)算結(jié)果為true@ConditionalOnJavaJava的版本匹配特定指或者一個(gè)范圍值@ConditionalOnProperty指定的配置屬性要有一個(gè)明確的值@ConditionalOnResourceClasspath里有指定的資源@ConditionalOnWebApplication這是一個(gè)Web應(yīng)用程序@ConditionalOnNotWebApplication這不是一個(gè)Web應(yīng)用程序
2.5、@ConfigurationProperties與@EnableConfigurationProperties
當(dāng)某些屬性的值需要配置的時(shí)候,我們一般會(huì)在 application.properties文件中新建配置項(xiàng),然后在bean中使用 @Value注解來(lái)獲取配置的值,比如下面配置數(shù)據(jù)源的代碼。
// jdbc configjdbc.mysql.url=jdbc:mysql://localhost:3306/sampledbjdbc.mysql.username=rootjdbc.mysql.password=123456......// 配置數(shù)據(jù)源@Configurationpublic 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注解注入的屬性通常都比較簡(jiǎn)單,如果同一個(gè)配置在多個(gè)地方使用,也存在不方便維護(hù)的問(wèn)題(考慮下,如果有幾十個(gè)地方在使用某個(gè)配置,而現(xiàn)在你想改下名字,你改怎么做?)。對(duì)于更為復(fù)雜的配置,Spring Boot提供了更優(yōu)雅的實(shí)現(xiàn)方式,那就是 @ConfigurationProperties注解。我們可以通過(guò)下面的方式來(lái)改寫上面的代碼:
@Component// 還可以通過(guò)@PropertySource("classpath:jdbc.properties")來(lái)指定配置文件@ConfigurationProperties("jdbc.mysql")// 前綴=jdbc.mysql,會(huì)在配置文件中尋找jdbc.mysql.*的配置項(xiàng)pulic class JdbcConfig { public String url; public String username; public String password;}@Configurationpublic 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對(duì)于更為復(fù)雜的配置,處理起來(lái)也是得心應(yīng)手,比如有如下配置文件:
#Appapp.menus[0].title=Homeapp.menus[0].name=Homeapp.menus[0].path=/app.menus[1].title=Loginapp.menus[1].name=Loginapp.menus[1].path=/loginapp.compiler.timeout=5app.compiler.output-folder=/temp/app.error=/error/
可以定義如下配置類來(lái)接收這些屬性
@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; }}
@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通過(guò)雙親委派模型進(jìn)行類的加載,我們也可以通過(guò)繼承 java.lang.classloader實(shí)現(xiàn)自己的類加載器。
何為雙親委派模型?當(dāng)一個(gè)類加載器收到類加載任務(wù)時(shí),會(huì)先交給自己的父加載器去完成,因此最終加載任務(wù)都會(huì)傳遞到最頂層的BootstrapClassLoader,只有當(dāng)父加載器無(wú)法完成加載任務(wù)時(shí),才會(huì)嘗試自己來(lái)加載。
采用雙親委派模型的一個(gè)好處是保證使用不同類加載器最終得到的都是同一個(gè)對(duì)象,這樣就可以保證Java 核心庫(kù)的類型安全,比如,加載位于rt.jar包中的 java.lang.Object類,不管是哪個(gè)加載器加載這個(gè)類,最終都是委托給頂層的BootstrapClassLoader來(lái)加載的,這樣就可以保證任何的類加載器最終得到的都是同樣一個(gè)Object對(duì)象。查看ClassLoader的源碼,對(duì)雙親委派模型會(huì)有更直觀的認(rèn)識(shí):
protected Class<?> loadClass(String name, boolean resolve) { synchronized (getClassLoadingLock(name)) { // 首先,檢查該類是否已經(jīng)被加載,如果從JVM緩存中找到該類,則直接返回 Class<?> c = findLoadedClass(name); if (c == null) { try { // 遵循雙親委派的模型,首先會(huì)通過(guò)遞歸從父加載器開(kāi)始找, // 直到父類加載器是BootstrapClassLoader為止 if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) {} if (c == null) { // 如果還找不到,嘗試通過(guò)findClass方法去尋找 // findClass是留給開(kāi)發(fā)者自己實(shí)現(xiàn)的,也就是說(shuō) // 自定義類加載器時(shí),重寫此方法即可 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }}
但雙親委派模型并不能解決所有的類加載器問(wèn)題,比如,Java 提供了很多服務(wù)提供者接口( ServiceProviderInterface,SPI),允許第三方為這些接口提供實(shí)現(xiàn)。常見(jiàn)的 SPI 有 JDBC、JNDI、JAXP 等,這些SPI的接口由核心類庫(kù)提供,卻由第三方實(shí)現(xiàn),這樣就存在一個(gè)問(wèn)題:SPI 的接口是 Java 核心庫(kù)的一部分,是由BootstrapClassLoader加載的;SPI實(shí)現(xiàn)的Java類一般是由AppClassLoader來(lái)加載的。BootstrapClassLoader是無(wú)法找到 SPI 的實(shí)現(xiàn)類的,因?yàn)樗患虞dJava的核心庫(kù)。它也不能代理給AppClassLoader,因?yàn)樗亲铐攲拥念惣虞d器。也就是說(shuō),雙親委派模型并不能解決這個(gè)問(wèn)題。
線程上下文類加載器( ContextClassLoader)正好解決了這個(gè)問(wèn)題。從名稱上看,可能會(huì)誤解為它是一種新的類加載器,實(shí)際上,它僅僅是Thread類的一個(gè)變量而已,可以通過(guò) setContextClassLoader(ClassLoadercl)和 getContextClassLoader()來(lái)設(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)的:
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í)不會(huì)一臉懵逼,這兩者除了在許多底層框架中取得的ClassLoader可能會(huì)有所不同外,其他大多數(shù)業(yè)務(wù)場(chǎng)景下都是一樣的,大家只要知道它是為了解決什么問(wèn)題而存在的即可。
類加載器除了加載class外,還有一個(gè)非常重要功能,就是加載資源,它可以從jar包中讀取任何資源文件,比如, ClassLoader.getResources(Stringname)方法就是用于讀取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);}
是不是覺(jué)得有點(diǎn)眼熟,不錯(cuò),它的邏輯其實(shí)跟類加載的邏輯是一樣的,首先判斷父類加載器是否為空,不為空則委托父類加載器執(zhí)行資源查找任務(wù),直到BootstrapClassLoader,最后才輪到自己查找。而不同的類加載器負(fù)責(zé)掃描不同路徑下的jar包,就如同加載class一樣,最后會(huì)掃描所有的jar包,找到符合條件的資源文件。
類加載器的 findResources(name)方法會(huì)遍歷其負(fù)責(zé)加載的所有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<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)的文件來(lái)讀取資源內(nèi)容。
看到這里,你可能會(huì)感到挺奇怪的,你不是要詳解 SpringFactoriesLoader嗎?上來(lái)講了一堆ClassLoader是幾個(gè)意思?看下它的源碼你就知道了:
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的知識(shí),再來(lái)理解這段代碼,是不是感覺(jué)豁然開(kāi)朗:從 CLASSPATH下的每個(gè)Jar包中搜尋所有 META-INF/spring.factories配置文件,然后將解析properties文件,找到指定名稱的配置后返回。需要注意的是,其實(shí)這里不僅僅是會(huì)去ClassPath路徑下查找,會(huì)掃描所有路徑下的Jar包,只不過(guò)這個(gè)文件只會(huì)在Classpath下的jar包中。來(lái)簡(jiǎn)單看下 spring.factories文件的內(nèi)容吧:
// 來(lái)自 org.springframework.boot.autoconfigure下的META-INF/spring.factories// EnableAutoConfiguration后文會(huì)講到,它用于開(kāi)啟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í)行 loadFactoryNames(EnableAutoConfiguration.class,classLoader)后,得到對(duì)應(yīng)的一組 @Configuration類,我們就可以通過(guò)反射實(shí)例化這些類然后注入到IOC容器中,最后容器里就有了一系列標(biāo)注了 @Configuration的JavaConfig形式的配置類。
這就是 SpringFactoriesLoader,它本質(zhì)上屬于Spring框架私有的一種擴(kuò)展方案,類似于SPI,Spring Boot在Spring基礎(chǔ)上的很多核心功能都是基于此,希望大家可以理解。
四、另一件武器:Spring容器的事件監(jiān)聽(tīng)機(jī)制
過(guò)去,事件監(jiān)聽(tīng)機(jī)制多用于圖形界面編程,比如:點(diǎn)擊按鈕、在文本框輸入內(nèi)容等操作被稱為事件,而當(dāng)事件觸發(fā)時(shí),應(yīng)用程序作出一定的響應(yīng)則表示應(yīng)用監(jiān)聽(tīng)了這個(gè)事件,而在服務(wù)器端,事件的監(jiān)聽(tīng)機(jī)制更多的用于異步通知以及監(jiān)控和異常處理。Java提供了實(shí)現(xiàn)事件監(jiān)聽(tīng)機(jī)制的兩個(gè)基礎(chǔ)類:自定義事件類型擴(kuò)展自 java.util.EventObject、事件的監(jiān)聽(tīng)器擴(kuò)展自 java.util.EventListener。來(lái)看一個(gè)簡(jiǎn)單的實(shí)例:簡(jiǎn)單的監(jiān)控一個(gè)方法的耗時(shí)。
首先定義事件類型,通常的做法是擴(kuò)展EventObject,隨著事件的發(fā)生,相應(yīng)的狀態(tài)通常都封裝在此類中:
public class MethodMonitorEvent extends EventObject { // 時(shí)間戳,用于記錄方法開(kāi)始執(zhí)行的時(shí)間 public long timestamp; public MethodMonitorEvent(Object source) { super(source); }}
事件發(fā)布之后,相應(yīng)的監(jiān)聽(tīng)器即可對(duì)該類型的事件進(jìn)行處理,我們可以在方法開(kāi)始執(zhí)行之前發(fā)布一個(gè)begin事件,在方法執(zhí)行結(jié)束之后發(fā)布一個(gè)end事件,相應(yīng)地,事件監(jiān)聽(tīng)器需要提供方法對(duì)這兩種情況下接收到的事件進(jìn)行處理:
// 1、定義事件監(jiān)聽(tīng)接口public interface MethodMonitorEventListener extends EventListener { // 處理方法執(zhí)行之前發(fā)布的事件 public void onMethodBegin(MethodMonitorEvent event); // 處理方法結(jié)束時(shí)發(fā)布的事件 public void onMethodEnd(MethodMonitorEvent event);}// 2、事件監(jiān)聽(tīng)接口的實(shí)現(xiàn):如何處理public class AbstractMethodMonitorEventListener implements MethodMonitorEventListener { @Override public void onMethodBegin(MethodMonitorEvent event) { // 記錄方法開(kāi)始執(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)聽(tīng)器接口針對(duì)不同的事件發(fā)布實(shí)際提供相應(yīng)的處理方法定義,最重要的是,其方法只接收MethodMonitorEvent參數(shù),說(shuō)明這個(gè)監(jiān)聽(tīng)器類只負(fù)責(zé)監(jiān)聽(tīng)器對(duì)應(yīng)的事件并進(jìn)行處理。有了事件和監(jiān)聽(tīng)器,剩下的就是發(fā)布事件,然后讓相應(yīng)的監(jiān)聽(tīng)器監(jiān)聽(tīng)并處理。通常情況,我們會(huì)有一個(gè)事件發(fā)布者,它本身作為事件源,在合適的時(shí)機(jī),將相應(yīng)的事件發(fā)布給對(duì)應(yīng)的事件監(jiān)聽(tīng)器:
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)聽(tīng)器被移除,這里為了安全做一個(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() {}
對(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)聽(tīng)器進(jìn)行處理。在具體實(shí)現(xiàn)時(shí)需要注意的是,事件發(fā)布是順序執(zhí)行,為了不影響處理性能,事件監(jiān)聽(tīng)器的處理邏輯應(yīng)盡量簡(jiǎn)單。
  • 事件監(jiān)聽(tīng)器的管理。publisher類中提供了事件監(jiān)聽(tīng)器的注冊(cè)與移除方法,這樣客戶端可以根據(jù)實(shí)際情況決定是否需要注冊(cè)新的監(jiān)聽(tīng)器或者移除某個(gè)監(jiān)聽(tīng)器。如果這里沒(méi)有提供remove方法,那么注冊(cè)的監(jiān)聽(tīng)器示例將一直被MethodMonitorEventPublisher引用,即使已經(jīng)廢棄不用了,也依然在發(fā)布者的監(jiān)聽(tīng)器列表中,這會(huì)導(dǎo)致隱性的內(nèi)存泄漏。
  • Spring容器內(nèi)的事件監(jiān)聽(tīng)機(jī)制
    Spring的ApplicationContext容器內(nèi)部中的所有事件類型均繼承自 org.springframework.context.AppliationEvent,容器中的所有監(jiān)聽(tīng)器都實(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)聽(tīng)器接口定義,它繼承自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)聽(tīng)器的管理工作委托給 ApplicationEventMulticaster接口的實(shí)現(xiàn)類。在容器啟動(dòng)時(shí),會(huì)檢查容器內(nèi)是否存在名為applicationEventMulticaster的ApplicationEventMulticaster對(duì)象實(shí)例。如果有就使用其提供的實(shí)現(xiàn),沒(méi)有就默認(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類:
    @SpringBootApplicationpublic class MoonApplication { public static void main(String[] args) { SpringApplication.run(MoonApplication.class, args); }}
    其中 @SpringBootApplication開(kāi)啟組件掃描和自動(dòng)配置,而 SpringApplication.run則負(fù)責(zé)啟動(dòng)引導(dǎo)應(yīng)用程序。@SpringBootApplication是一個(gè)復(fù)合 Annotation,它將三個(gè)有用的注解組合在一起:
    @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框架的注解,標(biāo)明該類是一個(gè) JavaConfig配置類。而 @ComponentScan啟用組件掃描,前文已經(jīng)詳細(xì)講解過(guò),這里著重關(guān)注 @EnableAutoConfiguration。
    @EnableAutoConfiguration注解表示開(kāi)啟Spring Boot自動(dòng)配置功能,Spring Boot會(huì)根據(jù)應(yīng)用的依賴、自定義的bean、classpath下有沒(méi)有某個(gè)類 等等因素來(lái)猜測(cè)你需要的bean,然后注冊(cè)到IOC容器中。那 @EnableAutoConfiguration是如何推算出你的需求?首先看下它的定義:
    @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(EnableAutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { // ......}
    你的關(guān)注點(diǎn)應(yīng)該在 @Import(EnableAutoConfigurationImportSelector.class)上了,前文說(shuō)過(guò), @Import注解用于導(dǎo)入類,并將這個(gè)類作為一個(gè)bean的定義注冊(cè)到容器中,這里它將把 EnableAutoConfigurationImportSelector作為bean注入到容器中,而這個(gè)類會(huì)將所有符合條件的@Configuration配置都加載到容器中,看看它的代碼:
    public String[] selectImports(AnnotationMetadata annotationMetadata) { // 省略了大部分代碼,保留一句核心代碼 // 注意:SpringBoot最近版本中,這句代碼被封裝在一個(gè)單獨(dú)的方法中 // SpringFactoriesLoader相關(guān)知識(shí)請(qǐng)參考前文 List<String> factories = new ArrayList<String>(new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader)));}
    這個(gè)類會(huì)掃描所有的jar包,將所有符合條件的@Configuration配置類注入的容器中,何為符合條件,看看 META-INF/spring.factories的文件內(nèi)容:
    // 來(lá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 {}
    分別說(shuō)一說(shuō):

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

    // 提供對(duì)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為例吧。

    @Configurationpublic class DataSourcePoolMetadataProvidersConfiguration { @Configuration @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class) static class TomcatDataSourcePoolMetadataProviderConfiguration { @Bean public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() { ..... } } ......}
    DataSourcePoolMetadataProvidersConfiguration是數(shù)據(jù)庫(kù)連接池提供者的一個(gè)配置類,即Classpath中存在 org.apache.tomcat.jdbc.pool.DataSource.class,則使用tomcat-jdbc連接池,如果Classpath中存在 HikariDataSource.class則使用Hikari連接池。
    這里僅描述了DataSourceAutoConfiguration的冰山一角,但足以說(shuō)明Spring Boot如何利用條件話配置來(lái)實(shí)現(xiàn)自動(dòng)配置的。回顧一下, @EnableAutoConfiguration中導(dǎo)入了EnableAutoConfigurationImportSelector類,而這個(gè)類的 selectImports()通過(guò)SpringFactoriesLoader得到了大量的配置類,而每一個(gè)配置類則根據(jù)條件化配置來(lái)做出決策,以實(shí)現(xiàn)自動(dòng)配置。
    整個(gè)流程很清晰,但漏了一個(gè)大問(wèn)題:EnableAutoConfigurationImportSelector.selectImports()是何時(shí)執(zhí)行的?其實(shí)這個(gè)方法會(huì)在容器啟動(dòng)過(guò)程中執(zhí)行:AbstractApplicationContext.refresh(),更多的細(xì)節(jié)在下一小節(jié)中說(shuō)明。
    六、啟動(dòng)引導(dǎo):Spring Boot應(yīng)用啟動(dòng)的秘密
    6.1 SpringApplication初始化
    SpringBoot整個(gè)啟動(dòng)流程分為兩個(gè)步驟:初始化一個(gè)SpringApplication對(duì)象、執(zhí)行該對(duì)象的run方法。看下SpringApplication的初始化流程,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項(xiàng)目 this.webEnvironment = deduceWebEnvironment(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 找到入口類 this.mainApplicationClass = deduceMainApplicationClass();}
    初始化流程中最重要的就是通過(guò)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ù)情況下我們沒(méi)有必要自定義一個(gè)ApplicationContextInitializer,即便是Spring Boot框架,它默認(rèn)也只是注冊(cè)了兩個(gè)實(shí)現(xiàn),畢竟Spring的容器已經(jīng)非常成熟和穩(wěn)定,你沒(méi)有必要來(lái)改變它。
    而 ApplicationListener的目的就沒(méi)什么好說(shuō)的了,它是Spring框架對(duì)Java事件監(jiān)聽(tīng)機(jī)制的一種框架實(shí)現(xiàn),具體內(nèi)容在前文Spring事件監(jiān)聽(tīng)機(jī)制這個(gè)小節(jié)有詳細(xì)講解。這里主要說(shuō)說(shuō),如果你想為Spring Boot應(yīng)用添加監(jiān)聽(tīng)器,該如何實(shí)現(xiàn)?
    Spring Boot提供兩種方式來(lái)添加自定義監(jiān)聽(tīng)器:

    • 通過(guò) SpringApplication.addListeners(ApplicationListener<?>...listeners)或者 SpringApplication.setListeners(Collection<?extendsApplicationListener<?>>listeners)兩個(gè)方法來(lái)添加一個(gè)或者多個(gè)自定義監(jiān)聽(tīng)器
    • 既然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的初始化,我們就說(shuō)這么多。
    6.2 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è)思路來(lái)看看源碼:
    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); } }
    ① 通過(guò)SpringFactoriesLoader查找并加載所有的 SpringApplicationRunListeners,通過(guò)調(diào)用starting()方法通知所有的SpringApplicationRunListeners:應(yīng)用開(kāi)始啟動(dòng)了。SpringApplicationRunListeners其本質(zhì)上就是一個(gè)事件發(fā)布者,它在SpringBoot應(yīng)用啟動(dòng)的不同時(shí)間點(diǎn)發(fā)布不同應(yīng)用事件類型(ApplicationEvent),如果有哪些事件監(jiān)聽(tīng)者(ApplicationListener)對(duì)這些事件感興趣,則可以接收并且處理。還記得初始化流程中,SpringApplication加載了一系列ApplicationListener嗎?這個(gè)啟動(dòng)流程中沒(méi)有發(fā)現(xiàn)有發(fā)布事件的代碼,其實(shí)都已經(jīng)在SpringApplicationRunListeners這兒實(shí)現(xiàn)了。
    簡(jiǎ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。①處的代碼只會(huì)獲取到一個(gè)EventPublishingRunListener的實(shí)例,我們來(lái)看看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。接下來(lái)會(huì)發(fā)生什么,就不用我多說(shuō)了吧。
    ② 創(chuàng)建并配置當(dāng)前應(yīng)用將要使用的 Environment,Environment用于描述應(yīng)用程序當(dāng)前的運(yùn)行環(huán)境,其抽象了兩個(gè)方面的內(nèi)容:配置文件(profile)和屬性(properties),開(kāi)發(fā)經(jīng)驗(yàn)豐富的同學(xué)對(duì)這兩個(gè)東西一定不會(huì)陌生:不同的環(huán)境(eg:生產(chǎn)環(huán)境、預(yù)發(fā)布環(huán)境)可以使用不同的配置文件,而屬性則可以從配置文件、環(huán)境變量、命令行參數(shù)等來(lái)源獲取。因此,當(dāng)Environment準(zhǔn)備好后,在整個(gè)應(yīng)用的任何時(shí)候,都可以從Environment中獲取資源。
    總結(jié)起來(lái),②處的兩句代碼,主要完成以下幾件事:

    • 判斷Environment是否存在,不存在就創(chuàng)建(如果是web項(xiàng)目就創(chuàng)建 StandardServletEnvironment,否則創(chuàng)建 StandardEnvironment)
    • 配置Environment:配置profile以及properties
    • 調(diào)用SpringApplicationRunListener的 environmentPrepared()方法,通知事件監(jiān)聽(tīng)者:應(yīng)用的Environment已經(jīng)準(zhǔn)備好

    ③、SpringBoot應(yīng)用在啟動(dòng)時(shí)會(huì)輸出這樣的東西:
    . ____ _ __ _ _ / / ___'_ __ _ _(_)_ __ __ _ ( ( )___ | '_ | '_| | '_ / _` | / ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |___, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.5.6.RELEASE)
    如果想把這個(gè)東西改成自己的涂鴉,你可以研究以下Banner的實(shí)現(xiàn),這個(gè)任務(wù)就留給你們吧。
    ④、根據(jù)是否是web項(xiàng)目,來(lái)創(chuàng)建不同的ApplicationContext容器。
    ⑤、創(chuàng)建一系列 FailureAnalyzer,創(chuàng)建流程依然是通過(guò)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()方法來(lái)對(duì)已經(jīng)創(chuàng)建好的ApplicationContext進(jìn)行進(jìn)一步的處理
    • 調(diào)用SpringApplicationRunListener的 contextPrepared()方法,通知所有的監(jiān)聽(tīng)者:ApplicationContext已經(jīng)準(zhǔn)備完畢
    • 將所有的bean加載到容器中
    • 調(diào)用SpringApplicationRunListener的 contextLoaded()方法,通知所有的監(jiān)聽(tīng)者:ApplicationContext已經(jīng)裝載完畢

    ⑦、調(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來(lái)對(duì)容器做一些額外的操作。BeanFactoryPostProcessor允許我們?cè)谌萜鲗?shí)例化相應(yīng)對(duì)象之前,對(duì)注冊(cè)到容器的BeanDefinition所保存的信息做一些額外的操作。這里的getBeanFactoryPostProcessors()方法可以獲取到3個(gè)Processor:
    ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessorSharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessorConfigFileApplicationListener$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è)方法除了會(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()來(lái)完成自動(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ò)關(guān)注,甚至沒(méi)弄明白也沒(méi)有關(guān)系,你只要理解這些擴(kuò)展點(diǎn)是在何時(shí)如何工作的,能讓它們?yōu)槟闼眉纯伞?br />整個(gè)啟動(dòng)流程確實(shí)非常復(fù)雜,可以查詢參考資料中的部分章節(jié)和內(nèi)容,對(duì)照著源碼,多看看,我想最終你都能弄清楚的。言而總之,Spring才是核心,理解清楚Spring容器的啟動(dòng)流程,那Spring Boot啟動(dòng)流程就不在話下了。

    與50位技術(shù)專家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖

    總結(jié)

    以上是生活随笔為你收集整理的springboot中接口实例化_疫情爆发在家闲出屁的我,梳理一下SpringBoot知识点的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    在线视频专区 | 成人久久久久 | 精品久久久久久久久久 | 亚洲国产一区二区精品专区 | 成人久久18免费网站麻豆 | 色综合中文字幕 | 日韩网站中文字幕 | 国产精品乱码久久 | 91看片在线观看 | avv天堂| 国产在线观看91 | 99在线播放| 91成人蝌蚪 | 一区中文字幕在线观看 | 欧美色黄 | 亚洲乱码中文字幕综合 | 日韩免费电影网 | 国产精品视频区 | 日本韩国精品一区二区在线观看 | 免费网站在线观看成人 | 91精品中文字幕 | 日日夜夜精品网站 | 欧美在线不卡一区 | 制服丝袜在线91 | 久久这里只有精品视频首页 | 夜夜夜夜猛噜噜噜噜噜初音未来 | 五月天久久综合网 | 9在线观看免费高清完整版在线观看明 | 日韩有码在线观看视频 | 久久理论影院 | 99爱国产精品 | 久久老司机精品视频 | av黄色免费看 | 成人免费视频在线观看 | a午夜在线| 国产福利不卡视频 | 国产视频九色蝌蚪 | 日韩一区在线免费观看 | 国产成人精品一区在线 | 亚洲欧美偷拍另类 | 在线激情av电影 | 国产一级不卡毛片 | 欧美一区二区三区不卡 | 精品亚洲成a人在线观看 | 精品成人久久 | 亚洲精品乱码久久久久久蜜桃欧美 | 91色在线观看视频 | 狠狠88综合久久久久综合网 | 久久国产精品99久久久久久丝袜 | 五月导航 | 久久激情网站 | 国产一在线精品一区在线观看 | 国产视频二区三区 | 久久看免费视频 | 国产成人精品一区二区三区网站观看 | 亚洲美女视频在线观看 | 中文字幕在线观看视频一区二区三区 | 国产精品免费看久久久8精臀av | 午夜婷婷在线播放 | 午夜三级理论 | 中文国产字幕 | 久久久精品欧美一区二区免费 | 91久久精品一区二区二区 | 欧美三级免费 | 亚洲黄色免费在线 | 亚洲精品美女在线观看播放 | 国产精品入口66mio女同 | 国产高清在线免费视频 | 精品亚洲欧美一区 | 久久国产精品久久精品国产演员表 | 色综合五月| 日韩视频一二三区 | 最新国产在线 | 狂野欧美激情性xxxx | 亚洲涩涩涩 | 国产精品热视频 | 日日躁你夜夜躁你av蜜 | 国产成人精品三级 | 91久久国产自产拍夜夜嗨 | 亚洲午夜久久久久久久久 | 欧美日韩精品影院 | 亚洲国产精品电影在线观看 | 日韩精品一卡 | 久久夜夜夜 | 国产高清专区 | 久久99日韩 | 91网站在线视频 | 国产午夜激情视频 | 日韩免费福利 | av在线色| 国产人成一区二区三区影院 | 免费在线黄 | 日韩av免费在线看 | 久久观看最新视频 | 日本女人在线观看 | 欧美日韩性视频 | 亚洲视屏在线播放 | 国产污视频在线观看 | 天堂va在线高清一区 | 久草在线一免费新视频 | 一区二区三区在线播放 | 91亚洲精品久久久蜜桃网站 | 中文字幕在线国产精品 | 色av男人的天堂免费在线 | 日韩在线免费播放 | 手机成人在线电影 | 日韩一区二区免费视频 | 亚洲精品tv久久久久久久久久 | 国产爽视频 | 久久人操 | 国产va精品免费观看 | 亚洲免费在线看 | 最新精品视频在线 | 国精产品999国精产品视频 | 欧美巨大荫蒂茸毛毛人妖 | 亚洲 成人 一区 | 久久午夜精品 | 亚洲精品在线观看网站 | 国产成人av网址 | 国产视频一区在线 | 在线免费av网站 | 天天操天天舔天天干 | 免费高清在线视频一区· | 激情在线网址 | 欧美国产日韩久久 | 中文字幕专区高清在线观看 | 在线看一区 | 日本xxxx.com | 黄色三级在线观看 | 欧洲激情综合 | 美女精品在线 | 97国产电影 | 欧美日韩在线观看一区二区 | 麻豆国产精品永久免费视频 | 欧美日韩高清不卡 | 毛片网站观看 | 日日天天干 | 亚洲一区动漫 | www.国产在线视频 | 最近中文字幕第一页 | 亚洲在线视频免费 | 婷婷九月丁香 | av网站免费线看精品 | 99久久99| 成人宗合网| 精品国产一区二区三区四区在线观看 | 久久av在线 | 欧美极品少妇xxxx | 中文字幕在线观看视频一区二区三区 | 国产伦理久久精品久久久久_ | 天天透天天插 | 日韩高清观看 | 国产精品成人免费一区久久羞羞 | 日韩二区精品 | 国产精品一区二区中文字幕 | 97夜夜澡人人爽人人免费 | 免费又黄又爽视频 | 免费成人黄色av | 欧美精品中文在线免费观看 | 香蕉视频日本 | 国产自产高清不卡 | 日韩资源在线观看 | 亚洲三级国产 | 黄色资源在线 | 久久久久免费电影 | 日韩久久久久久久久 | 天天激情| 国产精品国产三级国产不产一地 | 天天操天天摸天天射 | 精品国产免费一区二区三区五区 | 日韩在线欧美在线 | 久久视频网| 国产午夜精品在线 | 日本性视频 | 久久精品久久久精品美女 | 成人午夜精品福利免费 | 在线观看国产成人av片 | 久久综合狠狠综合 | 精品伦理一区二区三区 | 免费av网址在线观看 | 91精品1区| 97超碰.com | 国产成人精品综合久久久久99 | 国产精品av久久久久久无 | 丁五月婷婷 | 一本一道久久a久久精品 | 中文字幕 二区 | 中文字幕在线观看2018 | 久久新视频 | 亚洲深爱激情 | 久久久久久高清 | 超碰在线97观看 | 亚洲一区二区三区四区在线视频 | 婷婷开心久久网 | 亚洲影视资源 | 欧美激情视频一二区 | 激情视频在线观看网址 | 99久久精品久久久久久动态片 | 亚洲va欧洲va国产va不卡 | av黄网站| wwwwwww色| 欧美精品中文字幕亚洲专区 | 国内精品国产三级国产aⅴ久 | 看v片| 日韩美女黄色片 | 免费看污在线观看 | 国产美女精品久久久 | 久久蜜臀一区二区三区av | 免费人成在线观看网站 | 国产黄视频在线观看 | 视频一区二区三区视频 | 久久综合久色欧美综合狠狠 | 久久国产精品久久精品国产演员表 | 日韩久久精品一区二区三区 | 国产中文字幕在线免费观看 | 丝袜网站在线观看 | 欧美 亚洲 另类 激情 另类 | 亚洲丁香久久久 | 91系列在线| 97在线观看视频国产 | 在线 国产一区 | 日日操天天操夜夜操 | www.av中文字幕.com| av不卡免费在线观看 | 日本免费一二三区 | 亚洲精品美女久久 | 尤物九九久久国产精品的分类 | 99热在线国产 | 久久久久亚洲天堂 | 亚洲欧美日韩一二三区 | 欧美少妇xxx | 日韩中文字幕免费视频 | 国产高清不卡一区二区三区 | 又爽又黄又刺激的视频 | 欧美日韩不卡在线 | 99久久婷婷国产精品综合 | 国产精品久久久久久一区二区 | 人人干人人干人人干 | 久久色在线播放 | 亚洲在线高清 | 久久久免费少妇 | 精品成人免费 | 免费网站在线观看人 | 精品亚洲欧美一区 | 狠狠操91| 国产精品一区二区久久精品爱微奶 | 夜夜视频资源 | 一级做a爱片性色毛片www | 成年人在线免费看 | 日韩一区二区三区免费视频 | 亚洲国产中文字幕在线视频综合 | 日日爱av | 国产色拍拍拍拍在线精品 | 天天综合天天综合 | 亚洲精品国产成人 | 国产成人精品一区二区在线观看 | 在线免费观看黄网站 | 国产xxxx做受性欧美88 | 最新色视频 | 日韩www在线 | 在线视频观看亚洲 | 亚洲精品国内 | 久久久久久久影视 | www.com.黄 | 伊人电影在线观看 | 六月色 | 亚洲美女视频在线 | 国产精品入口a级 | 精品一二三区 | www.xxxx变态.com| 国产精品爽爽久久久久久蜜臀 | 欧美日性视频 | 天天操网址| 成人午夜剧场在线观看 | 国产免费久久av | 狠狠操影视 | 中文字幕在线观看免费高清电影 | 成人va在线观看 | 日韩动态视频 | 精品一区 在线 | 一级α片 | 在线观看aaa | 韩日成人av | 在线黄色av| 国产一级免费观看 | 国产不卡在线观看视频 | 日韩久久久久久久久 | 久久久精品二区 | 成人网在线免费视频 | 色天天综合久久久久综合片 | 视频在线观看99 | 日韩伦理片一区二区三区 | 婷婷在线免费视频 | 久久九九影视网 | 国产精品久久久久一区二区三区 | www激情网 | 国产成人在线免费观看 | 综合网天天 | 久久久影片| 日韩av免费观看网站 | 国产打女人屁股调教97 | 精品国内自产拍在线观看视频 | 精品久久久久久亚洲综合网站 | 91大神免费在线观看 | 精品国产久 | 日韩在线观看第一页 | 日韩中文字幕免费视频 | 久久精品99国产精品 | 欧美日韩国产一二 | 91av片| 99精品福利视频 | 丁香网婷婷| 免费三级黄 | 国产精品18久久久久久不卡孕妇 | 麻豆视频免费在线播放 | 伊人狠狠色 | 国产午夜精品一区 | 激情综合网五月婷婷 | 91看片麻豆 | 国产成视频在线观看 | 久久久久国产精品免费 | 国产久草在线 | 91精品啪| 久草在线视频国产 | 色综合激情网 | 一级片视频在线 | 青青草在久久免费久久免费 | 亚洲美女久久 | 最新av在线网站 | 国产精品九九九九九九 | 日韩欧美精品在线观看 | 日本中文字幕系列 | 成人在线观看你懂的 | 色婷婷在线播放 | 国内视频一区二区 | 99在线视频播放 | 日韩毛片在线免费观看 | 久久艹国产 | 国产一区在线观看视频 | 日日夜夜91 | 国产亚洲精品日韩在线tv黄 | 亚洲日本中文字幕在线观看 | 欧美精选一区二区三区 | 色综合久久久久久久 | 亚洲va欧美va人人爽 | 亚洲国内精品视频 | 在线天堂中文www视软件 | 久久情爱 | 免费精品在线观看 | 黄色小说在线观看视频 | 成人黄色在线 | 国产精品免费一区二区 | 久插视频| 免费视频二区 | 4p变态网欧美系列 | 日韩精品第一区 | av免费电影在线 | 一本一道久久a久久精品 | 免费视频三区 | 97超碰在线久草超碰在线观看 | 亚洲激情影院 | 婷婷.com| 成人一级片视频 | 国产三级精品三级在线观看 | 国产精品久久久777 成人手机在线视频 | 亚洲欧美视频在线 | www激情久久 | 欧美最新另类人妖 | www国产亚洲精品久久网站 | 国内精品久久久久久久影视麻豆 | 青草视频在线播放 | 婷婷六月综合网 | 欧美美女激情18p | 中文字幕在线乱 | 免费观看性生交大片3 | av观看久久久 | 色婷婷综合久色 | 麻豆91在线看 | 欧美一区二区精美视频 | 午夜美女福利 | 久久精品综合网 | 伊人天天干 | 国产成人精品亚洲 | 色婷婷 亚洲 | 亚洲人在线7777777精品 | 天天操天天干天天操天天干 | 天天色天天射天天综合网 | 精品一区 精品二区 | 亚洲特级片 | 精品夜夜嗨av一区二区三区 | 在线观看亚洲国产 | 91av蜜桃| 精品久久久久久综合 | 国产九色视频在线观看 | 在线a视频免费观看 | 不卡视频在线看 | 国产成人亚洲精品自产在线 | 久草在线免费播放 | 97色在线| 97精品国产97久久久久久免费 | 久久超碰在线 | 精品美女在线视频 | 日本成人免费在线观看 | 午夜黄色大片 | 精品久久亚洲 | 国产一线二线三线性视频 | 伊人五月天婷婷 | 精品国产乱子伦一区二区 | 亚洲视频一 | 激情中文在线 | 干av在线| 99色亚洲| 91香蕉视频黄色 | 精品一区二区日韩 | 国产精品久久久久一区 | 国产成人精品一区二 | 片网站| 亚洲国产资源 | a在线观看免费视频 | 亚洲国产小视频在线观看 | 成人h在线 | 精品美女久久久久 | 少妇自拍av| 精品国产美女在线 | 人人视频网站 | 久久 在线 | 欧美日韩激情视频8区 | 亚洲综合精品视频 | 亚洲精品在线观看免费 | 亚洲视频,欧洲视频 | 四虎在线视频 | 婷婷去俺也去六月色 | 国产成人精品综合 | 在线播放视频一区 | 久久久久久蜜av免费网站 | 欧美一二三在线 | 在线黄色国产电影 | 一级黄色片在线观看 | 久久精品3| 99久久久国产精品免费99 | 免费看一级一片 | 亚洲最新av| 国产精品都在这里 | 免费看av在线 | 一区二区三区免费在线观看视频 | 国产麻豆精品在线观看 | 一本一本久久a久久精品综合小说 | 97国产视频 | 91大神精品视频在线观看 | 亚洲视频分类 | 亚洲三级网 | 亚洲另类视频在线 | 日韩视频中文字幕 | 69国产精品视频 | av电影一区 | 日韩黄色av网站 | 国产亚洲情侣一区二区无 | 精品黄色片 | 亚洲专区路线二 | 天天干夜夜想 | 午夜精品久久久久久 | 日韩网站免费观看 | 伊人婷婷| 欧美怡红院 | 国产又粗又猛又黄又爽 | 国产精品美女久久久久久 | 久久国产精品久久精品 | 99精品免费视频 | 中文字幕不卡在线88 | 国产在线91在线电影 | 精品国产乱码久久久久久1区二区 | 九九九免费视频 | 国产一在线精品一区在线观看 | 黄色小说18 | 久久免费看毛片 | 99视频一区二区 | 久草视频在线播放 | a黄色影院 | 成人h在线| 国产做a爱一级久久 | 亚洲h视频在线 | 日本久久久久久久久 | 成人av免费在线播放 | 五月婷婷激情六月 | www亚洲精品| 中文字幕一区二区在线播放 | 免费精品视频在线观看 | 欧美另类色图 | 色综合五月天 | 久艹在线播放 | 国产人成在线观看 | 久久成人18免费网站 | 一本到视频在线观看 | 天天爱天天干天天爽 | 日本久久久久久科技有限公司 | 激情六月婷婷久久 | 久久人人爽爽 | 久草免费在线观看 | 天天艹天天操 | 欧美视屏一区二区 | 正在播放国产一区二区 | 久久久久久草 | 韩日av一区二区 | 成人小视频在线免费观看 | 日韩欧美视频免费看 | 天天色影院 | 久久久久久国产精品亚洲78 | 精品国产乱码久久久久久浪潮 | 日韩免费专区 | 欧美视频xxx | 91av在线精品 | 韩国av免费观看 | 欧美不卡视频在线 | 丰满少妇在线观看网站 | www.亚洲精品 | 中文字幕永久免费 | 久久国产热 | 中文字幕二区三区 | 国产精品午夜在线观看 | 福利在线看片 | 久久久久一区二区三区 | 伊人午夜 | 操操操综合 | 黄色avwww | 久久高清毛片 | 成人国产精品av | 综合久久五月天 | 99视频在线精品免费观看2 | 精品一区二区电影 | 精品国产一区二区三区久久影院 | 成年一级片 | 欧美激情精品一区 | 欧美aaa视频 | av超碰在线| 色www免费视频 | 超碰最新网址 | 成人一级视频在线观看 | 国产黄色成人av | 中文字幕一区av | 91中文视频| 91免费网 | 在线成人观看 | 欧美福利视频一区 | 国产免费久久 | 久热电影 | 欧美作爱视频 | 成人av.com| 国产精品a级 | 欧美激情精品久久久久久免费印度 | 午夜电影一区 | 成人午夜电影网 | 狠狠的干狠狠的操 | 91高清免费 | 91精品亚洲影视在线观看 | 日免费视频 | 美女久久久久久久久久久 | 久久免费视频在线 | 亚洲在线日韩 | 天天干夜夜擦 | 91精品视频免费 | 色网站国产精品 | 最近中文字幕 | 精品视频在线免费观看 | 亚洲精品乱码久久久久久蜜桃动漫 | 欧美中文字幕第一页 | 黄色片免费在线 | 日韩欧美电影 | 黄色在线看网站 | 91亚洲影院 | 久久久午夜精品福利内容 | 99精品一区 | 欧美了一区在线观看 | 91探花视频 | zzijzzij日本成熟少妇 | 久久黄色免费观看 | 99视频在线| 99在线精品视频观看 | 99精品国产成人一区二区 | 欧美国产在线看 | 久久理论片 | 97在线观看免费高清 | 高清免费av在线 | 视频国产精品 | 91久久久久久久一区二区 | 欧美一区二区三区特黄 | 久久久99精品免费观看乱色 | 欧美一区二区免费在线观看 | a√天堂中文在线 | 久久国产午夜精品理论片最新版本 | 黄色免费观看网址 | 日韩免费在线播放 | 国产成人三级在线 | 麻豆影视在线免费观看 | 91精品久久久久久综合乱菊 | 丁香婷婷激情国产高清秒播 | 国产91国语对白在线 | 日韩av快播电影网 | 在线看国产视频 | 婷婷综合伊人 | www黄在线 | 黄色一级大片在线免费看产 | 中文字幕国产视频 | 婷婷国产精品 | 黄色毛片观看 | 91在线免费视频 | 天天综合网久久 | 亚洲精品免费在线视频 | 国产黄a三级三级 | 久草在线在线视频 | 天天干,天天操 | 天天av综合网 | 国产人成精品一区二区三 | 9797在线看片亚洲精品 | 久久久免费看视频 | 日韩精品在线观看视频 | 久久少妇免费视频 | 美国人与动物xxxx | 91精品国产欧美一区二区 | 五月天免费网站 | 久久久久中文字幕 | 亚洲欧洲精品一区二区 | 成年人免费电影在线观看 | 亚洲精品乱码久久久久久久久久 | 国产香蕉久久精品综合网 | 亚洲资源一区 | 999抗病毒口服液 | 国产精品久久久久久69 | 69中文字幕 | 国产精品系列在线观看 | 国产99区| 国产丝袜高跟 | 在线观看视频亚洲 | 免费观看9x视频网站在线观看 | 日韩在线观看第一页 | 不卡中文字幕在线 | 日韩乱码中文字幕 | 日韩精品中文字幕在线 | 最近免费中文视频 | a精品视频 | 女人18片毛片90分钟 | 国产美女无遮挡永久免费 | 992tv在线| 国产精品一区二区久久精品爱微奶 | 涩涩网站在线播放 | 一区二区三区在线视频111 | 成年人在线观看免费视频 | 成人黄色小说视频 | 日韩欧美一区二区三区视频 | 日本精品视频在线播放 | 一二区电影 | 亚洲黄色免费网站 | 国产一区二区久久精品 | 欧美日韩中文在线观看 | 国产成人区| 亚洲最新av在线 | www.99在线观看 | 亚洲成人二区 | 国产自产在线视频 | 婷婷色综| 中文字幕在线一二 | 在线视频一二三 | 中文字幕永久免费 | 91九色网站 | 四虎免费在线观看视频 | 国产一级一片免费播放放 | 精品一区二区免费视频 | 97超碰在 | 欧美成人播放 | 91激情视频在线观看 | 丁香 久久 综合 | 三级黄色理论片 | 99精品在线看 | 日韩精品播放 | 成年人视频在线免费观看 | 九九九视频精品 | 久久新视频 | 久久91网 | 国产精品国产三级国产aⅴ9色 | 久久久黄视频 | 欧美黑人性爽 | 伊人五月天综合 | 在线观看mv的中文字幕网站 | 91污视频在线 | 日韩免费成人 | 成人综合婷婷国产精品久久免费 | 亚洲精品国产成人av在线 | 亚洲天天做 | 欧美日韩一级在线 | 91精品天码美女少妇 | 黄色成年片 | 国产高清视频网 | 中文字幕专区高清在线观看 | 久久久www免费电影网 | 国内精品久久影院 | 国内毛片毛片 | 激情久久综合网 | 日韩久久久久久久久 | 亚洲精品伦理在线 | 久久论理 | 超碰官网 | www国产亚洲 | 久久综合久久八八 | 成人在线视频你懂的 | 日韩激情小视频 | 成人免费视频播放 | 99av国产精品欲麻豆 | 国产精品久久久av久久久 | 91毛片在线观看 | 精品在线99 | 草在线视频 | 欧美日韩p片 | 欧美成人黄色 | 男女精品久久 | 97超碰资源网| av三区在线 | 日韩色视频在线观看 | 精品一二三四五区 | 亚洲精品国产精品国自 | 亚洲特级毛片 | 亚洲专区视频在线观看 | 久久精品视 | 最近中文字幕第一页 | 人人看看人人 | av在线播放中文字幕 | 黄色av三级在线 | 国产成人亚洲在线观看 | 久久免费视频在线观看 | 久久精品这里热有精品 | 狠狠干狠狠艹 | 久久久国产精品人人片99精片欧美一 | 伊人干综合| 日韩无在线 | 在线三级播放 | 久久久久久久久久久免费视频 | 伊人婷婷综合 | 成人综合婷婷国产精品久久免费 | 99亚洲视频 | 色片网站在线观看 | 久草在线最新 | 日韩欧美在线观看一区二区三区 | 天天干天天摸 | 免费下载高清毛片 | 亚洲综合五月 | 美女视频黄的免费的 | 成年人视频在线免费播放 | 狠狠干网 | 激情综合网五月激情 | 亚洲精品在线观看的 | 黄色大片日本免费大片 | 精品在线免费视频 | 国产h在线播放 | 香蕉视频国产在线 | 免费视频一区二区 | 国产成人一区二区三区 | av片在线观看免费 | 狠狠色丁香九九婷婷综合五月 | 激情五月播播久久久精品 | 99在线看 | 欧洲黄色片 | 91精品视频免费在线观看 | 中文在线字幕免 | 96精品视频 | 欧美最猛性xxxxx免费 | 中文字幕久久精品 | 九九免费观看视频 | 欧美一级性生活视频 | 亚洲精品中文字幕视频 | 国产日韩欧美网站 | 特级大胆西西4444www | avav99| 天堂av在线网址 | 精品美女在线视频 | 韩国视频一区二区三区 | 成人免费在线看片 | 精品国产不卡 | 香蕉在线视频播放网站 | 久久久天天操 | 国产精品视频专区 | 中文字幕在线精品 | 国产高清视频在线播放 | 2024国产精品视频 | 久草久草久草久草 | 色操插 | 91精品久久久久久综合乱菊 | 激情五月婷婷激情 | 欧美日韩一区二区免费在线观看 | 午夜视频黄 | 97av在线视频免费播放 | 亚洲国产中文字幕 | 久久蜜臀一区二区三区av | 久久国产精品视频免费看 | 成人午夜精品福利免费 | 国产精品久久久久亚洲影视 | 成人在线播放网站 | 狠狠干夜夜爽 | 免费在线观看av电影 | 精品一区二区精品 | 亚洲电影免费 | 99热99re6国产在线播放 | 日本精品视频一区二区 | 在线你懂的视频 | 人人爱在线视频 | 国产九九在线 | 丁香免费视频 | 国产精品一区二区三区久久久 | 人人看人人草 | 五月激情在线 | 中国一级片在线播放 | 91热| 在线观看国产区 | 一本色道久久综合亚洲二区三区 | 99视频久久 | 成人免费在线观看入口 | 国产涩图| 久久久亚洲精品 | 91在线免费播放视频 | japanesefreesex中国少妇 | 人人看97 | 狠狠干狠狠操 | 黄色三级免费网址 | 免费在线观看一区 | 久久精品福利 | 欧美极品少妇xxxxⅹ欧美极品少妇xxxx亚洲精品 | 中文字幕视频三区 | 国产在线观看污片 | 精品国产一区二区久久 | a黄色一级 | 三级av在线播放 | 欧美激情视频三区 | 亚洲黄色网络 | 久草在线免 | 99精品免费视频 | 四虎影视国产精品免费久久 | 亚洲另类交 | 天天色天天射天天操 | 在线视频日韩 | 日韩精品不卡在线观看 | www.夜夜操| 综合色久 | 色操插 | 免费精品视频在线 | 在线观看一区 | 夜夜操综合网 | 欧美日韩视频一区二区三区 | 一区二区三区国 | 国产精品自产拍在线观看桃花 | 亚洲欧美国产精品18p | 在线免费视频 你懂得 | 成人在线视频免费观看 | 国产精品免费看久久久8精臀av | 亚洲va在线va天堂va偷拍 | 日韩欧美一区二区三区免费观看 | 色婷婷综合五月 | 91爱在线| 色a网| 韩日精品中文字幕 | 久久另类视频 | 久久久免费毛片 | 免费在线观看成年人视频 | 国产精品av电影 | 2021国产在线视频 | 黄色网www| 免费观看成人网 | 国产黄色精品在线观看 | 国产亚洲综合性久久久影院 | 日韩在线电影一区二区 | 婷婷社区五月天 | 久久99精品久久久久蜜臀 | 免费看片网页 | 色网站在线免费观看 | av三区在线 | 午夜国产福利在线观看 | 免费在线观看视频一区 | 狠狠伊人| 日本黄色大片儿 | 久久精品艹 | 91av视频在线观看免费 | 日韩一区二区三区在线观看 | 黄色片软件网站 | 好看av在线 | 久久国产精品精品国产色婷婷 | 91九色视频导航 | 麻豆av一区二区三区在线观看 | 手机av看片| 欧美性成人 | 精品一区二三区 | 二区精品视频 | 精品人人人 | 精品国产日本 | 在线视频一二三 | 婷婷 综合 色 | 亚洲精品综合一区二区 | 国内精品久久久久久久影视简单 | 欧美日韩在线视频一区二区 | 91成人免费看| 亚洲麻豆精品 | 国产五月婷 | 少妇视频在线播放 | 操操碰 | 亚洲精品999 | 国产h在线播放 | 在线 国产一区 | 成人免费影院 | 国产精品嫩草影院123 | 色婷五月天 | 97精品国产97久久久久久 | 日韩在线视频精品 | 日产中文字幕 | 99久久精品国产观看 | 毛片黄色一级 | 不卡电影免费在线播放一区 | 日韩电影中文字幕 | 一级片黄色片网站 | 国产在线自 | 久久无码av一区二区三区电影网 | 中文字幕日韩国产 | 99精品久久久久久久久久综合 | 最近日本mv字幕免费观看 | 91精品国产麻豆 | 99久久精品免费看国产四区 | 国产91对白在线 | 成人久久影院 | 91自拍视频在线 | 一级a毛片高清视频 | 亚洲欧美国产精品18p | 波多野结衣电影一区 | 欧美日韩免费看 | 在线观看免费版高清版 | 国产成人精品久久久久蜜臀 | 国产不卡毛片 | 91人人射| 日本不卡123 | 亚洲一级二级三级 | 99久久精品无免国产免费 | 天堂在线视频免费观看 | 久草国产在线 | 欧美精品乱码99久久影院 | www婷婷 | 久久精品国产久精国产 | 国产精品 中文字幕 亚洲 欧美 | 黄色片网站av | 国产69精品久久久久99尤 | 免费a网址 | 欧美日韩在线观看一区二区三区 | 日p在线观看 | 在线看片a | 国产中出在线观看 | 天天射夜夜爽 | 麻豆影视在线观看 | 日韩精品免费在线 | 有码一区二区三区 | 在线综合 亚洲 欧美在线视频 | 日韩免费在线观看网站 | 成人在线一区二区三区 | 国产精品久久久久久久午夜片 | 成人超碰97 | 欧美日韩一区二区三区在线观看视频 | 日日躁你夜夜躁你av蜜 | 欧美视频在线二区 | 欧美亚洲免费在线一区 | 久久欧美在线电影 | 欧美视频在线二区 | 精品人妖videos欧美人妖 | 97视频在线观看视频免费视频 | 手机在线观看国产精品 | 国产在线播放一区二区三区 | 高潮毛片无遮挡高清免费 | 亚洲精品乱码白浆高清久久久久久 | 国产成人精品午夜在线播放 | 国产xxxxx在线观看 | 免费久久99精品国产婷婷六月 | 国产 字幕 制服 中文 在线 | 欧美一级片免费播放 | 91九色蝌蚪 | 中文字幕国产一区 | 超碰公开在线观看 | 国产美女视频 | 日韩v欧美v日本v亚洲v国产v | 国产大陆亚洲精品国产 | 狠狠黄 | 日韩sese| 日日久视频 | 9999毛片| 成人免费看片98欧美 | 黄色av三级在线 | avwww在线| 国产五十路毛片 | 国产精品麻豆欧美日韩ww | 日韩欧美成人网 | 九九久| 999国内精品永久免费视频 | 99爱在线观看 | 中文字幕成人网 | www.国产毛片 | 在线视频欧美日韩 | 国产午夜不卡 | 亚洲乱亚洲乱亚洲 | 免费日韩电影 |