掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢(xún)/運(yùn)營(yíng)咨詢(xún)/技術(shù)建議/互聯(lián)網(wǎng)交流
除了元注解之外,EnableAutoConfiguration包含了兩大重要部分:

該注解只導(dǎo)入了一個(gè)內(nèi)部類(lèi):AutoConfigurationPackages.Registrar.class
類(lèi)中有兩個(gè)方法
從名字上看,registerBeanDefinitions方法注冊(cè)了定義好的一些Bean,determineImports方法決定這些要不要導(dǎo)入
registerBeanDefinitions調(diào)用了register方法,并傳入了registry參數(shù)和一個(gè)元數(shù)據(jù)名字?jǐn)?shù)組。registry參數(shù)是一個(gè)接口,
實(shí)際場(chǎng)景中必然是使用的實(shí)現(xiàn)類(lèi),可以在該方法打斷點(diǎn)debug
發(fā)現(xiàn)實(shí)際上他的實(shí)現(xiàn)類(lèi)是一個(gè)名為DefaultListableBeanFactory,并且可以清晰的看到該類(lèi)的一些基本屬性的值
可以看到,registry中存儲(chǔ)的是一些關(guān)于項(xiàng)目程序的基本配置和bean實(shí)例名。比如一些網(wǎng)頁(yè)支持組件和springContext以及一些加載器
還有用戶(hù)自定義的類(lèi),這些類(lèi)組件中存儲(chǔ)了bean名字、作用域、懶加載等等
再來(lái)看另一個(gè)參數(shù):new PackageImports(metadata).getPackageNames().toArray(new String[0])
看起來(lái)是將元數(shù)據(jù)中的包名提取成數(shù)組,我們打開(kāi)看看該類(lèi)在初始化的時(shí)候具體干了什么,可以看到實(shí)際上在初始化時(shí)調(diào)用了一個(gè)ClassUtils.getPackageName,
傳入了一個(gè)什么呢,metadata.getclassname,他是什么呢,打斷點(diǎn)!
,實(shí)際上就是啟動(dòng)類(lèi)的全名,com.***.application,而這個(gè)方法則是將classname的前綴提取出來(lái),即提取出我們的包名com.&&&
即這個(gè)AutoConfigurationPackages.register方法傳入了我們的一大堆初始bean的名字、配置和總包名com.**,我們研究研究他做了什么
首先是判斷定義的bean中有沒(méi)有Bean,這個(gè)bean是類(lèi)的屬性,存著當(dāng)前類(lèi)的全名:org.springframework.boot.autoconfigure.AutoConfigurationPackages
大概想處理的是用戶(hù)自定義了AutoConfigurationPackages類(lèi)的情況。當(dāng)前是沒(méi)有定義的,所以直接走else邏輯
new了一個(gè)GenericBeanDefinition,暫且叫他通用的bean定義工具,set一大堆東西。之后調(diào)用了注冊(cè)方法。把工具丟了進(jìn)去。
這個(gè)注冊(cè)方法里大概是將AutoConfigurationPackages類(lèi)同樣注冊(cè)進(jìn)了定義bean的map中,然后將map中的所有值加入到了這個(gè)默認(rèn)的bean工廠(chǎng)中,之后這個(gè)類(lèi)就走完了。
因此該注解的作用即是將自定義的bean以及一些基本類(lèi)型、原始組件注冊(cè)到bean工廠(chǎng)中
該類(lèi)中點(diǎn)進(jìn)去映入眼簾的就是selectImports方法。聽(tīng)起來(lái)名字是選擇導(dǎo)入,也就是該方法決定了要加載什么依賴(lài)組件
而該方法調(diào)用了并且只調(diào)用了getAutoConfigurationEntry方法來(lái)獲取要加載的組件。
我們來(lái)看看這個(gè)方法,先是調(diào)用getAttributes獲取了某個(gè)東西,打斷點(diǎn)發(fā)現(xiàn)是
這樣就很熟悉了,是EnableAutoConfiguration注解的兩個(gè)屬性值,雖然默認(rèn)值為null。點(diǎn)進(jìn)方法體發(fā)現(xiàn)確實(shí)是這樣。
getCandidateConfigurations(annotationMetadata, attributes)方法調(diào)用了一大串,最終調(diào)用了這個(gè)方法loadSpringFactories,
該方法的大致內(nèi)容是,從當(dāng)前所有的依賴(lài)包中加載META-INF/目錄下的spring.factories文件中尋找一些組件。
可以看到他先嘗試從緩存中拿,如果為空,則去依賴(lài)包中的META-INF/目錄下的spring.factories中加載。
拿到這個(gè)組件列表之后,還要進(jìn)行一層過(guò)濾,抽取含有factoryTypeName的組件列表。
這個(gè)name即是org.springframework.boot.autoconfigure.EnableAutoConfiguration自動(dòng)配置注解
再回到getEntry方法,之后對(duì)獲取到的組件列表進(jìn)行去重,然后試圖從列表中拿出排除項(xiàng),
也就是attributes中獲取到的EnableAutoConfiguration注解的兩個(gè)屬性值的內(nèi)容。很容易理解,屬性值中配置了要排除的內(nèi)容將在這里進(jìn)行排除。
而后調(diào)用filter方法對(duì)列表進(jìn)行篩選,而篩選使用的是autoConfigurationMetadata這個(gè)類(lèi),
由此可見(jiàn),這個(gè)類(lèi)是某種篩選規(guī)則,它里面存儲(chǔ)了501個(gè)properties,所以篩選規(guī)則可能就是逐一比對(duì),篩選出Metadata中有的組件。
篩選出來(lái)的結(jié)果即是最終要加載的組件bean。
也就是說(shuō),@EnableAutoConfiguration的兩個(gè)重要成員,一個(gè)決定了要加載默認(rèn)的哪些組件(用戶(hù)自定義bean、數(shù)值包裝類(lèi)、字符串類(lèi)等等)
和配置,另一個(gè)決定了要加載哪些外部依賴(lài)類(lèi),即通過(guò)starter等通過(guò)pom引入的組件。
如何知道spring是怎么、在哪處理請(qǐng)求的呢,有個(gè)很簡(jiǎn)單的方法,在properties中將日志級(jí)別設(shè)置為debug,即dubug=true。而后運(yùn)行程序,發(fā)送任意一個(gè)請(qǐng)求
就會(huì)發(fā)現(xiàn)spring打印出了關(guān)于處理該請(qǐng)求的一些細(xì)節(jié),比如處理請(qǐng)求是從初始化DispatcherServlet開(kāi)始的,
由此可見(jiàn)請(qǐng)求處理最重要的便是DispatcherServlet。而后AbstractHandlerMapping識(shí)別到了處理該請(qǐng)求的具體方法。
我們進(jìn)入到DispatcherServlet中,查看他的方法。
學(xué)過(guò)mvc原生web開(kāi)發(fā)的都知道,servlet有兩大重要方法,doGet和doPost。而DispatcherServlet繼承了FrameworkServlet繼承了HttpServletBean,
HttpServletBean繼承了HttpServlet,由此可知,DispatcherServlet也是一個(gè)httpServlet。那么我們就有思路了,從他的do**方法開(kāi)始探究。
從名字看,這個(gè)方法似乎是為了做轉(zhuǎn)發(fā)。并且他的參數(shù)幾乎和doGet方法是一模一樣的
最開(kāi)始是初始化了一大堆東西,包括處理異步請(qǐng)求的異步管理器以及檢查是否是文件上傳的請(qǐng)求巴拉巴拉,而后這個(gè)getHandler方法,直接獲取到了處理這個(gè)請(qǐng)求的具體方法。
它是如何處理的呢,他遍歷了一個(gè)handlerMappings的集合,這個(gè)mapping里面存儲(chǔ)的是spring一些專(zhuān)處理映射的類(lèi)
,比如歡迎頁(yè)的映射,以及我們使用requestMapping標(biāo)注的url。這樣就打通了,他會(huì)在requestMappingHandlerMapping中查找到/hello請(qǐng)求并且找到他映射的方法。
具體查找調(diào)用了HandlerMapping的gethandler,由此就和日志中的對(duì)上了,gethandler方法中就是根據(jù)url在映射中找方法,先拋開(kāi)這個(gè)細(xì)節(jié)不看。
拿到這個(gè)處理器(方法)后,將其丟入到了HandlerAdapter請(qǐng)求適配器中,這個(gè)在mvc架構(gòu)中熟悉的身影。
之后并不是立即執(zhí)行該方法而是先判斷方法的種類(lèi),如果是get或者h(yuǎn)ead方法,則執(zhí)行邏輯。
這個(gè)邏輯會(huì)衡返回一個(gè)-1,也就是寫(xiě)死的,我不理解為什么是這樣,在網(wǎng)上搜了Last-Modified,發(fā)現(xiàn)這是一種緩存機(jī)制,
這也就理解了為什么必須是get方法,而他實(shí)現(xiàn)需要實(shí)現(xiàn)LastModified接口,我并沒(méi)有實(shí)現(xiàn)這個(gè),所以spring這個(gè)判斷邏輯會(huì)衡false。
之后是一個(gè)applyPreHandle的方法,點(diǎn)進(jìn)去就會(huì)發(fā)現(xiàn)是使用當(dāng)前spring的攔截器組件對(duì)請(qǐng)求進(jìn)行攔截,
能發(fā)現(xiàn)就是一些請(qǐng)求方法攔截器、token攔截器、資源攔截器等等,也就是一些壞的請(qǐng)求會(huì)在這條被攔截
之后就是請(qǐng)求適配器執(zhí)行自己的處理邏輯了,可以看到他返回的是一個(gè)modelAndView類(lèi)型的實(shí)例,那么就意味著,此時(shí)方法已經(jīng)被執(zhí)行了。
但是實(shí)際上并沒(méi)有使用modelAndView作為返回值,而是直接返回的string。所以mv是一個(gè)空值,但是可以在響應(yīng)體中觀(guān)察到,
已經(jīng)有19字節(jié)的東西被寫(xiě)進(jìn)了響應(yīng)體中,頁(yè)印證了方法已經(jīng)被執(zhí)行。
緊接著是判斷請(qǐng)求是否是異步請(qǐng)求,如果是異步請(qǐng)求可能會(huì)做First響應(yīng)之類(lèi)的處理。
再往下是一個(gè)名叫應(yīng)用默認(rèn)的視圖名的方法,將請(qǐng)求體中或者默認(rèn)的視圖名加入model中。
因?yàn)槿绻麘?yīng)用了modelandview,此時(shí)才只是一個(gè)model,必然要添加對(duì)應(yīng)的view??梢院?jiǎn)單測(cè)試一下。我們?cè)谔幚矸椒ㄖ衝ew一個(gè)modelandview對(duì)象,設(shè)置一個(gè)model值并返回。
可以看到,經(jīng)過(guò)此方法之后,我們并沒(méi)有設(shè)置view值,系統(tǒng)默認(rèn)將hello當(dāng)作了view加入model,這個(gè)默認(rèn)值即是請(qǐng)求路徑去掉前面的/。
這個(gè)方法之后,又是一個(gè)類(lèi)似于攔截器,有點(diǎn)類(lèi)似于在方法執(zhí)行前后各執(zhí)行一次攔截。攔截器執(zhí)行完畢后,方法結(jié)束。
后面就是一些兜底處理,比如如果是文件相關(guān)之類(lèi)的請(qǐng)求,要關(guān)閉對(duì)應(yīng)的流。如果是異步請(qǐng)求執(zhí)行怎樣的邏輯。
我們知道,適配器的handle方法里面執(zhí)行了方法邏輯,具體是怎么執(zhí)行的呢。實(shí)際上是調(diào)用了super的AbstractHandlerMethodAdapter的handle方法。
這個(gè)handle方法又會(huì)調(diào)用自己(RequestMappingHandlerAdapter)的handleInternal方法。該方法內(nèi)對(duì)session做了一些處理,
而后調(diào)用invokeHandlerMethod方法,這個(gè)方法內(nèi)做了很多的處理。比如WebDataBinderFactory binderFactory對(duì)象,他是對(duì)參數(shù)之中的數(shù)據(jù)格式做轉(zhuǎn)換的,他里面初始化了128種對(duì)象轉(zhuǎn)換的方式;
又或者初始化一些參數(shù)解析器和返回值處理器:里面對(duì)各種參數(shù)和返回值做對(duì)應(yīng)的解析和處理,總之就是把這些丟入到一個(gè)mavContainer容器中。
然后調(diào)用invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0])方法,同時(shí)傳入的還有webRequest,
這個(gè)就是將請(qǐng)求體和響應(yīng)體包裝到了一起。這個(gè)方法里面第一行就直接執(zhí)行了方法,可以看到此時(shí)已經(jīng)有返回值了。
這個(gè)方法就比較簡(jiǎn)單了:獲取參數(shù)、執(zhí)行方法
該方法最開(kāi)始是獲取映射方法的參數(shù)類(lèi)型以及參數(shù)名
之后做的事大概可以猜到,就是從請(qǐng)求中找出這些參數(shù)名對(duì)應(yīng)的參數(shù)并且轉(zhuǎn)化成對(duì)應(yīng)的類(lèi)型??梢钥纯淳唧w的
首先定義了一個(gè)接收數(shù)組,用來(lái)存儲(chǔ)獲取到的參數(shù),而后嘗試從providedArgs中拿對(duì)應(yīng)的參數(shù),但實(shí)際上這個(gè)參數(shù)傳的是空值。
所以執(zhí)行后面的邏輯,從請(qǐng)求中拿,并且同時(shí)傳入了mav容器,這個(gè)容器中就有格式轉(zhuǎn)換器和返回值處理器等。
該方法里面有兩個(gè)重要的構(gòu)成,getArgumentResolver獲取參數(shù)解析器和resolveArgument解析參數(shù)。獲取參數(shù)解析器方法中,
循環(huán)目前已經(jīng)存進(jìn)ioc容器的解析器組件,和參數(shù)進(jìn)行一一比對(duì),找到可以解析對(duì)應(yīng)參數(shù)的解析器。
解析參數(shù)方法中,就是獲取參數(shù)名容器→獲取方法參數(shù)容器→獲取參數(shù)名→解析參數(shù)。之后會(huì)做很多的后續(xù)處理,比如格式轉(zhuǎn)換之類(lèi)的。
這個(gè)方法比較簡(jiǎn)單,利用之前已經(jīng)存入InvocableHandlerMethod中的反射方法public java.lang.String
com.glodon.controller.HelloController.home(java.lang.String),實(shí)際上,該對(duì)象實(shí)例也是專(zhuān)門(mén)存儲(chǔ)方法處理的相關(guān)組件的。
獲取到方法后,利用spring反射機(jī)制執(zhí)行該方法。
前面將方法執(zhí)行完之后,封裝完modelAndView,在執(zhí)行完處理后攔截器后,還要執(zhí)行一個(gè)兜底方法對(duì)結(jié)果進(jìn)行處理,從名字來(lái)看,他是用來(lái)處理結(jié)果轉(zhuǎn)發(fā)的。
開(kāi)始是判斷方法執(zhí)行是否有異常,如果有則走異常處理邏輯。而后如果modelandview不為空,則執(zhí)行一個(gè)render方法render(mv, request, response)
render方法首先從請(qǐng)求中拿到語(yǔ)言標(biāo)識(shí),并加入到響應(yīng)體中,而后拿出modelandview中的視圖名,即重定向或者其他視圖。
然后調(diào)用resolveViewName方法對(duì)視圖名進(jìn)行處理,對(duì)當(dāng)前容器中的所有解析器進(jìn)行遍歷,哪個(gè)可以解析這個(gè)視圖名,就直接返回解析結(jié)果。
解析器的解析過(guò)程,以ContentNegotiatingViewResolver舉例。
這個(gè)解析器獲取了請(qǐng)求的Attributes,然后傳入getMediaTypes方法中并調(diào)用來(lái)獲取返回?cái)?shù)據(jù)類(lèi)型。
getMediaTypes里面其實(shí)就是一個(gè)雙重循環(huán)匹配,格式化網(wǎng)頁(yè)請(qǐng)求→獲取瀏覽器請(qǐng)求頭中的可接受媒體類(lèi)型→獲取系統(tǒng)可生產(chǎn)的媒體類(lèi)型→初始化匹配的媒體類(lèi)型。
然后進(jìn)行一個(gè)雙重循環(huán),如果匹配成功,則把對(duì)應(yīng)的媒體類(lèi)型加入到compatibleMediaTypes中。
排序完成后就是一個(gè)set→List,然后進(jìn)行一個(gè)排序,排序的依據(jù)就是媒體類(lèi)型的權(quán)重。
瀏覽器在發(fā)送請(qǐng)求的時(shí)候會(huì)給服務(wù)器一個(gè)accept,里面明確表示了瀏覽器可以接收的返回類(lèi)型以及他的權(quán)重,而這里的排序就是按照這個(gè)權(quán)重進(jìn)行排序的。
該方法返回后,在接著看解析方法,之后調(diào)用了getCandidateViews獲取候選視圖。
getCandidateViews方法內(nèi)同樣是個(gè)雙層循環(huán)。外層是除了當(dāng)前解析器外的其他三個(gè)視圖解析器,內(nèi)層是對(duì)匹配到的媒體類(lèi)型。
通過(guò)調(diào)試,發(fā)現(xiàn)最終是被InternalResourceViewResolver成功處理,我們只看他的細(xì)節(jié)。
同樣還是resolveViewname方法,該方法先嘗試去緩存中拿,拿不到了才執(zhí)行createView方法創(chuàng)建視圖。而這個(gè)方法就很清晰明了了
判斷是重定向還是轉(zhuǎn)發(fā)來(lái)生成對(duì)應(yīng)的視圖。最終返回合適的視圖,添加緩存巴拉巴拉。
我們測(cè)試的剛好是一個(gè)重定向視圖,所以返回的結(jié)果就是bean為redirect,url為/helloWorld的視圖。?

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢(xún)/運(yùn)營(yíng)咨詢(xún)/技術(shù)建議/互聯(lián)網(wǎng)交流