去水印小程序源碼springboot(去水印小程序源碼個(gè)人)
本篇文章給大家談?wù)勅ニ⌒〕绦蛟创aspringboot,以及去水印小程序源碼個(gè)人對應(yīng)的知識點(diǎn),希望對各位有所幫助,不要忘了收藏本站喔。
本文目錄一覽:
從零開始學(xué)SpringBoot之SpringBoot WebSocket原理篇
前言:
?????這節(jié)我們介紹下WebSocket的原理。
一、websocket與http
WebSocket是HTML5出的協(xié)議,也就是說HTTP協(xié)議沒有變化,或者說沒關(guān)系,但HTTP是不支持持久連接的(長連接,循環(huán)連接的不算)
首先HTTP有 1.1 和 1.0 之說,也就是所謂的 keep-alive ,把多個(gè)HTTP請求合并為一個(gè),但是 Websocket 其實(shí)是一個(gè)新協(xié)議,跟HTTP協(xié)議基本沒有關(guān)系,只是為了兼容現(xiàn)有瀏覽器的握手規(guī)范而已,也就是說它是HTTP協(xié)議上的一種補(bǔ)充,可以通過這樣一張圖理解:
有交集,但是并不是全部。
另外Html5指的是一系列新的API,或者說新規(guī)范,新技術(shù)。Http協(xié)議本身只有1.0和1.1,而且跟Html本身沒有直接關(guān)系。通俗來說,你可以用HTTP協(xié)議傳輸非Html數(shù)據(jù)。
二、Websocket是什么樣的協(xié)議,具體有什么優(yōu)點(diǎn)
首先,Websocket是一個(gè)持久化的協(xié)議,相對于HTTP這種非持久的協(xié)議來說。
HTTP的生命周期通過 Request 來界定,也就是一個(gè) Request 一個(gè) Response ,那么在 HTTP1.0 中,這次HTTP請求就結(jié)束了。
在HTTP1.1中進(jìn)行了改進(jìn),使得有一個(gè)keep-alive,也就是說,在一個(gè)HTTP連接中,可以發(fā)送多個(gè)Request,接收多個(gè)Response。但是請記住 Request = Response, 在HTTP中永遠(yuǎn)是這樣,也就是說一個(gè)request只能有一個(gè)response。而且這個(gè)response也是被動的,不能主動發(fā)起。
跟Websocket有什么關(guān)系呢?
首先Websocket是基于HTTP協(xié)議的,或者說借用了HTTP的協(xié)議來完成一部分握手。
首先我們來看個(gè)典型的 Websocket 握手(借用Wikipedia的。。)
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin:
熟悉HTTP的童鞋可能發(fā)現(xiàn)了,這段類似HTTP協(xié)議的握手請求中,多了幾個(gè)東西。我會順便講解下作用。
2.1 Upgrade 和Connection
Upgrade: websocket
Connection: Upgrade
這個(gè)就是Websocket的核心了,告訴 Apache 、Tomcat、 Nginx 等服務(wù)器:注意啦,我發(fā)起的是Websocket協(xié)議,快點(diǎn)幫我找到對應(yīng)的助理處理~不是那個(gè)老土的HTTP。
2.2 Sec-WebSocket
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
首先,?Sec-WebSocket-Key是一個(gè) Base64 encode 的值,這個(gè)是瀏覽器隨機(jī)生成的,告訴服務(wù)器:你妹,不要忽悠窩,我要驗(yàn)證尼是不是真的是Websocket助理。
然后,?Sec_WebSocket-Protocol是一個(gè)用戶定義的字符串,用來區(qū)分同URL下,不同的服務(wù)所需要的協(xié)議。簡單理解:今晚我要服務(wù)A,別搞錯(cuò)啦~
最后,?Sec-WebSocket-Version是告訴服務(wù)器所使用的 WebSocket Draft (協(xié)議版本),在最初的時(shí)候,Websocket協(xié)議還在 Draft 階段,各種奇奇怪怪的協(xié)議都有,而且還有很多期奇奇怪怪不同的東西,什么Firefox和Chrome用的不是一個(gè)版本之類的,當(dāng)初Websocket協(xié)議太多可是一個(gè)大難題。。不過現(xiàn)在還好,已經(jīng)定下來啦~大家都使用的一個(gè)東西~ 脫水:服務(wù)員,我要的是13歲的噢→_→
然后服務(wù)器會返回下列東西,表示已經(jīng)接受到請求,成功建立Websocket啦!
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
這里開始就是HTTP最后負(fù)責(zé)的區(qū)域了,告訴客戶,我已經(jīng)成功切換協(xié)議啦~
Upgrade: websocket
Connection: Upgrade
依然是固定的,告訴客戶端即將升級的是 Websocket 協(xié)議,而不是mozillasocket,lurnarsocket或者shitsocket。
然后,?Sec-WebSocket-Accept這個(gè)則是經(jīng)過服務(wù)器確認(rèn),并且加密過后的 Sec-WebSocket-Key 。服務(wù)器:好啦好啦,知道啦,給你看我的ID CARD來證明行了吧。后面的, Sec-WebSocket-Protocol 則是表示最終使用的協(xié)議。
至此,HTTP已經(jīng)完成它所有工作了,接下來就是完全按照Websocket協(xié)議進(jìn)行了。具體的協(xié)議就不在這闡述了。
—————— 技術(shù)解析部分完畢 ——————
你說了這么久,那到底Websocket有什么鬼用, http long poll ,或者ajax輪詢 不都可以實(shí)現(xiàn)實(shí)時(shí)信息傳遞么。
好好好,年輕人,那我們來講一講Websocket有什么用。來給你吃點(diǎn)胡(蘇)蘿(丹)卜(紅)
三、Websocket的作用
在講Websocket之前,我就順帶著講下 long poll 和 ajax輪詢 的原理。
3.1 ajax 輪詢
ajax輪詢的原理非常簡單,讓瀏覽器隔個(gè)幾秒就發(fā)送一次請求,詢問服務(wù)器是否有新信息。
場景再現(xiàn):
客戶端:啦啦啦,有沒有新信息(Request)
服務(wù)端:沒有(Response)
客戶端:啦啦啦,有沒有新信息(Request)
服務(wù)端:沒有。。(Response)
客戶端:啦啦啦,有沒有新信息(Request)
服務(wù)端:你好煩啊,沒有啊。。(Response)
客戶端:啦啦啦,有沒有新消息(Request)
服務(wù)端:好啦好啦,有啦給你。(Response)
客戶端:啦啦啦,有沒有新消息(Request)
服務(wù)端:。。。。。沒。。。。沒。。。沒有(Response)?—- loop
3.1? 長輪詢(long poll)
long poll?其實(shí)原理跟?ajax輪詢?差不多,都是采用輪詢的方式,不過采取的是阻塞模型(一直打電話,沒收到就不掛電話),也就是說,客戶端發(fā)起連接后,如果沒消息,就一直不返回Response給客戶端。直到有消息才返回,返回完之后,客戶端再次建立連接,周而復(fù)始。
場景再現(xiàn):
客戶端:啦啦啦,有沒有新信息,沒有的話就等有了才返回給我吧(Request)
服務(wù)端:額。。等待到有消息的時(shí)候。。來給你(Response)
客戶端:啦啦啦,有沒有新信息,沒有的話就等有了才返回給我吧(Request)?-loop
從上面可以看出其實(shí)這兩種方式,都是在不斷地建立HTTP連接,然后等待服務(wù)端處理,可以體現(xiàn)HTTP協(xié)議的另外一個(gè)特點(diǎn),被動性。
何為被動性呢,其實(shí)就是,服務(wù)端不能主動聯(lián)系客戶端,只能有客戶端發(fā)起。
簡單地說就是,服務(wù)器是一個(gè)很懶的冰箱(這是個(gè)梗)(不會、不能主動發(fā)起連接),但是上司有命令,如果有客戶來,不管多么累都要好好接待。
說完這個(gè),我們再來說一說上面的缺陷(原諒我廢話這么多吧OAQ)
從上面很容易看出來,不管怎么樣,上面這兩種都是非常消耗資源的。
ajax輪詢需要服務(wù)器有很快的處理速度和資源。(速度)long poll?需要有很高的并發(fā),也就是說同時(shí)接待客戶的能力。(場地大?。?/p>
所以?ajax輪詢?和?long poll?都有可能發(fā)生這種情況。
客戶端:啦啦啦啦,有新信息么?
服務(wù)端:月線正忙,請稍后再試(503 ServerUnavailable)
客戶端:。。。。好吧,啦啦啦,有新信息么?
服務(wù)端:月線正忙,請稍后再試(503 ServerUnavailable)
客戶端:然后服務(wù)端在一旁忙的要死:冰箱,我要更多的冰箱!更多。。更多。。(我錯(cuò)了。。這又是梗。。)
3.2 WebSocket
通過上面這個(gè)例子,我們可以看出,這兩種方式都不是最好的方式,需要很多資源。
一種需要更快的速度,一種需要更多的’電話’。這兩種都會導(dǎo)致’電話’的需求越來越高。
哦對了,忘記說了HTTP還是一個(gè)狀態(tài)協(xié)議。
通俗的說就是,服務(wù)器因?yàn)槊刻煲哟嗫蛻袅?,是個(gè)健忘鬼,你一掛電話,他就把你的東西全忘光了,把你的東西全丟掉了。你第二次還得再告訴服務(wù)器一遍。
所以在這種情況下出現(xiàn)了,Websocket出現(xiàn)了。他解決了HTTP的這幾個(gè)難題。首先,被動性,當(dāng)服務(wù)器完成協(xié)議升級后(HTTP-Websocket),服務(wù)端就可以主動推送信息給客戶端啦。所以上面的情景可以做如下修改。
客戶端:啦啦啦,我要建立Websocket協(xié)議,需要的服務(wù):chat,Websocket協(xié)議版本:17(HTTP Request)
服務(wù)端:ok,確認(rèn),已升級為Websocket協(xié)議(HTTPProtocols Switched)
客戶端:麻煩你有信息的時(shí)候推送給我噢。。
服務(wù)端:ok,有的時(shí)候會告訴你的。
服務(wù)端:balabalabalabala
服務(wù)端:balabalabalabala
服務(wù)端:哈哈哈哈哈啊哈哈哈哈
服務(wù)端:笑死我了哈哈哈哈哈哈哈
就變成了這樣,只需要經(jīng)過一次HTTP請求,就可以做到源源不斷的信息傳送了。(在程序設(shè)計(jì)中,這種設(shè)計(jì)叫做回調(diào),即:你有信息了再來通知我,而不是我傻乎乎的每次跑來問你)
這樣的協(xié)議解決了上面同步有延遲,而且還非常消耗資源的這種情況。那么為什么他會解決服務(wù)器上消耗資源的問題呢?
其實(shí)我們所用的程序是要經(jīng)過兩層代理的,即HTTP協(xié)議在Nginx等服務(wù)器的解析下,然后再傳送給相應(yīng)的Handler(PHP等)來處理。簡單地說,我們有一個(gè)非??焖俚?接線員(Nginx) ,他負(fù)責(zé)把問題轉(zhuǎn)交給相應(yīng)的 客服(Handler) 。
本身接線員基本上速度是足夠的,但是每次都卡在客服(Handler)了,老有客服處理速度太慢。,導(dǎo)致客服不夠。Websocket就解決了這樣一個(gè)難題,建立后,可以直接跟接線員建立持久連接,有信息的時(shí)候客服想辦法通知接線員,然后接線員在統(tǒng)一轉(zhuǎn)交給客戶。這樣就可以解決客服處理速度過慢的問題了。
同時(shí),在傳統(tǒng)的方式上,要不斷的建立,關(guān)閉HTTP協(xié)議,由于HTTP是非狀態(tài)性的,每次都要重新傳輸 identity info (鑒別信息),來告訴服務(wù)端你是誰。
雖然接線員很快速,但是每次都要聽這么一堆,效率也會有所下降的,同時(shí)還得不斷把這些信息轉(zhuǎn)交給客服,不但浪費(fèi)客服的處理時(shí)間,而且還會在網(wǎng)路傳輸中消耗過多的流量/時(shí)間。
但是Websocket只需要一次HTTP握手,所以說整個(gè)通訊過程是建立在一次連接/狀態(tài)中,也就避免了HTTP的非狀態(tài)性,服務(wù)端會一直知道你的信息,直到你關(guān)閉請求,這樣就解決了接線員要反復(fù)解析HTTP協(xié)議,還要查看identity info的信息。
同時(shí)由客戶主動詢問,轉(zhuǎn)換為服務(wù)器(推送)有信息的時(shí)候就發(fā)送(當(dāng)然客戶端還是等主動發(fā)送信息過來的。。),沒有信息的時(shí)候就交給接線員(Nginx),不需要占用本身速度就慢的客服(Handler)了
至于怎么在不支持Websocket的客戶端上使用Websocket。。答案是:不能。但是可以通過上面說的 long poll 和 ajax 輪詢 來 模擬出類似的效果
看完讓你徹底搞懂Websocket原理
內(nèi)容轉(zhuǎn)自知乎:
如果覺得文字不過癮,可以通過視頻學(xué)習(xí)SpringBoot,這里給大家推薦
《從零開始學(xué)SpringBoot》視頻教程鏈接:
【? Java全棧技術(shù)分享 】,Jacky。
Springboot初始化流程解析
以上是一個(gè)最簡單的Springboot程序(2.0.3版本)示例,也是我們最通用的寫法,但其中其實(shí)封裝這一系列復(fù)雜的功能操作,讓我們開始逐步進(jìn)行分析。
首先這里最重要的必然是注解 @SpringBootApplication
@SpringBootApplication 注解由幾個(gè)注解復(fù)合組成,其中最主要的就是 @SpringBootConfiguration 、 @EnableAutoConfiguration 和 @ComponentScan 這三個(gè)。
其中的 @ComponentScan 是spring的原生注解, @SpringBootConfiguration 雖然是springboot中的注解,但其實(shí)質(zhì)就是包裝后的 @Configuration ,仍然是spring中的注解,用于代替xml的方式管理配置bean
@EnableAutoConfiguration 的定義如上,這里最重要的注解是 @Import ( @AutoConfigurationPackage 注解的實(shí)現(xiàn)也是基于 @Import ),借助 @Import 的幫助,將所有符合自動配置條件的bean定義加載到IoC容器中。關(guān)于 @EnableAutoConfiguration 注解后續(xù)涉及到時(shí)會再詳細(xì)說明。這里我們先回到啟動類的 run 方法從頭分析初始化流程。
可以看到'run'方法最終調(diào)用的是 new SpringApplication(primarySources).run(args) ,這里首先創(chuàng)建了 SpringApplication 對象,然后調(diào)用其 run 方法
這里主要是為 SpringApplication 對象進(jìn)行初始化,這里要專門提一下的是 webApplicationType 和 getSpringFactoriesInstances 。
它用來標(biāo)識我們的應(yīng)用是什么類型的應(yīng)用,來看一下 deduceWebApplicationType() 方法的實(shí)現(xiàn)
其返回值是 WebApplicationType 類型的枚舉類,其值有 NONE 、 SERVLET 、 REACTIVE 三種,分別對應(yīng)非WEB應(yīng)用,基于servlet的WEB應(yīng)用和基于reactive的WEB應(yīng)用。
這里的核心是 SpringFactoriesLoader.loadFactoryNames(type, classLoader) 方法,來看一下
重點(diǎn)關(guān)注一下 loadSpringFactories(classLoader) 做了什么
這里的 FACTORIES_RESOURCE_LOCATION 定義為 META-INF/spring.factories ,因此該方法會掃描所有包下的該文件,將其解析成map對象并緩存到 cache 中以避免重復(fù)加載,springboot包下該文件的部分片段如下
從這里可以看出, setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)) 和 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 分別對應(yīng)設(shè)置的是上述這些類。
解析完成后調(diào)用 createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names) 處理解析結(jié)果,生成對應(yīng)的實(shí)例,源碼如下
這里的核心是通過 ClassUtils.forName(name, classLoader) 方法,以反射的方式生成類實(shí)例 instanceClass 。由此可以看出 SpringFactoriesLoader.loadFactoryNames(type, classLoader) 的作用就是將 META-INF/spring.factories 中配置的內(nèi)容進(jìn)行實(shí)例化的工廠方法類,具備很強(qiáng)的擴(kuò)展性,與SPI機(jī)制有異曲同工
的效果。
看完 SpringApplication 的初始化,接著跳回 run 方法繼續(xù)分析
這里挑其中比較重要的幾個(gè)方法進(jìn)行分析
通過 getOrCreateEnvironment() 方法創(chuàng)建容器環(huán)境
可以看到 environment 存在則不會重復(fù)創(chuàng)建,當(dāng)應(yīng)用類型為servlet時(shí)創(chuàng)建的是 StandardServletEnvironment 對象,否則創(chuàng)建 StandardEnvironment 對象。
接著來看 configureEnvironment(environment, applicationArguments.getSourceArgs())
configurePropertySources(environment, args) 加載啟動命令行的配置屬性,來看一下實(shí)現(xiàn)
這里的 MutablePropertySources 對象用于存儲配置集合,其內(nèi)部維護(hù)了一個(gè) CopyOnWriteArrayList 類型的list對象,當(dāng)默認(rèn)配置存在時(shí),會向該list的尾部插入一個(gè) new MapPropertySource("defaultProperties", this.defaultProperties) 對象。
接著來看 configureProfiles(environment, args)
這里主要做的事情就是獲取 environment.getActiveProfiles() 的參數(shù)設(shè)置到 environment 中,即 spring.profiles.active 對應(yīng)的環(huán)境變量。
最后來看一下 listeners.environmentPrepared(environment)
這里的 listeners 就是之前通過 META-INF/spring.factories 注冊的所有l(wèi)isteners,后面我們先以其中最重要的 ConfigFileApplicationListener 做為例子進(jìn)行分析,接著來看 listener.environmentPrepared(environment)
可以看到這里創(chuàng)建了一個(gè) ApplicationEnvironmentPreparedEvent 類型的事件,并且調(diào)用了 multicastEvent 方法,通過該方法最終會調(diào)用到listener的 onApplicationEvent 方法,觸發(fā)事件監(jiān)聽器的執(zhí)行。
接下來具體看一下 ConfigFileApplicationListener 的 onApplicationEvent 方法做了什么
可以看到當(dāng)監(jiān)聽到 ApplicationEnvironmentPreparedEvent 類型的事件時(shí),調(diào)用 onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event) 方法
可以看到這里通過 loadPostProcessors() 方法加載了 META-INF/spring.factories 中的所有 EnvironmentPostProcessor 類到list中,同時(shí)把 ConfigFileApplicationListener 自己也添加進(jìn)去了。接著遍歷list中所有對象,并執(zhí)行 postProcessEnvironment 方法,于是接著來看該方法
這里的核心是 new Loader(environment, resourceLoader).load() ,這里的 Loader 是一個(gè)內(nèi)部類,用于處理配置文件的加載,首先看一下其構(gòu)造方法
可以看到這里的 resourceLoader 又是通過 SpringFactoriesLoader 進(jìn)行加載,那么來看看 META-INF/spring.factories 中定義了哪些 resourceLoader
從名字就可以看出來, PropertiesPropertySourceLoader 和 YamlPropertySourceLoader 分別用于處理.properties和.yml類型的配置文件。
接著來看看 load() 方法做了什么
initializeProfiles() 進(jìn)行了 profiles 的初始化,默認(rèn)會添加 null 和 default 到 profiles 中, null 對應(yīng)配置文件application.properties和application.yml, default 對應(yīng)配置文件application-default.yml和application-default.properties,這里的 null 會被優(yōu)先處理,由于后處理的會覆蓋先處理的,因此其優(yōu)先級最低。
接著來看 load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)) 方法
這里重點(diǎn)是通過 getSearchLocations() 獲取配置文件的路徑,默認(rèn)會獲得4個(gè)路徑
接著會遍歷這些路徑,拼接配置文件名稱,選擇合適的yml或者properties解析器進(jìn)行解析,最后將結(jié)果添加到 environment 的 propertySources 中。
可以看到這里也是根據(jù) webApplicationType 的取值,分別創(chuàng)建不同的返回類型。
這里的 sources 裝的就是我們的啟動類,然后通過 load(context, sources.toArray(new Object[0])) 方法進(jìn)行加載
來看一下 loader 是如何被加載的
經(jīng)過一系列調(diào)用之后最終由 load(Class? source) 方法執(zhí)行,這里比較有趣的是當(dāng)Groovy存在時(shí)居然是優(yōu)先調(diào)用Groovy的方式進(jìn)行加載,否則才走 this.annotatedReader.register(source) 方法將啟動類注冊到 beanDefinitionMap 中。
這個(gè) refresh() 方法相當(dāng)重要,尤其是 invokeBeanFactoryPostProcessors(beanFactory) ,這是實(shí)現(xiàn)spring-boot-starter-*(mybatis、redis等)自動化配置的關(guān)鍵部分,后續(xù)再詳細(xì)講解。
至此Springboot的啟動流程已經(jīng)大體分析完了,也了解了配置文件和啟動類分別是是如何被加載的,但仍有兩個(gè)問題待解,一是Springboot的核心思想約定大于配置是如何做到的,二是Springboot的各種spring-boot-starter-*是如何發(fā)揮作用的,這兩個(gè)問題留待后續(xù)文章繼續(xù)分析。
[Spring boot源碼解析] 2 啟動流程分析
在了解 Spring Boot 的啟動流程的時(shí)候,我們先看一下一個(gè)Spring Boot 應(yīng)用是如何啟動的,如下是一個(gè)簡單的 SpringBoot 程序,非常的簡潔,他是如何做到的呢,我們接下來就將一步步分解。
我們追蹤 SpringApplication.run() 方法,其實(shí)最終它主要的邏輯是新建一個(gè) SpringApplication ,然后調(diào)用他的 run 方法,如下:
我們先來看一下創(chuàng)建 SpringApplication 的方法:
在將Main class 設(shè)置 primarySources 后,調(diào)用了 WebApplicationType.deduceFromClasspath() 方法,該方法是為了檢查當(dāng)前的應(yīng)用類型,并設(shè)置給 webApplicationType 。 我們進(jìn)入 deduceFromClasspath 方法 :
這里主要是通過類加載器判斷是否存在 REACTIVE 相關(guān)的類信息,假如有就代表是一個(gè) REACTIVE 的應(yīng)用,假如不是就檢查是否存在 Servelt 和 ConfigurableWebApplicationContext ,假如都沒有,就代表應(yīng)用為非 WEB 類應(yīng)用,返回 NONE ,默認(rèn)返回 SERVLET 類型,我們這期以我們目前最常使用的 SERVLET 類型進(jìn)行講解,所以我們在應(yīng)用中引入了 spring-boot-starter-web 作為依賴:
他會包含 Spring-mvc 的依賴,所以就包含了內(nèi)嵌 tomcat 中的 Servlet 和 Spring-web 中的 ConfigurableWebApplicationContext ,因此返回了 SERVLET 類型。
回到剛才創(chuàng)建 SpringApplication 的構(gòu)建方法中,我們設(shè)置完成應(yīng)用類型后,就尋找所有的 Initializer 實(shí)現(xiàn)類,并設(shè)置到 SpringApplication 的 Initializers 中,這里先說一下 getSpringFactoriesInstances 方法,我們知道在我們使用 SpringBoot 程序中,會經(jīng)常在 META-INF/spring.factories 目錄下看到一些 EnableAutoConfiguration ,來出發(fā) config 類注入到容器中,我們知道一般一個(gè) config 類要想被 SpringBoot 掃描到需要使用 @CompnentScan 來掃描具體的路徑,對于 jar 包來說這無疑是非常不方便的,所以 SpringBoot 提供了另外一種方式來實(shí)現(xiàn),就是使用 spring.factories ,比如下面這個(gè),我們從 Springboot-test 中找到的例子,這里先定義了一個(gè)ExampleAutoConfiguration,并加上了 Configuration 注解:
然后在 spring.factories 中定義如下:
那這種方式是怎么實(shí)現(xiàn)的你,這就要回到我們剛才的方法 getSpringFactoriesInstances :
我們先來看一下傳入?yún)?shù),這里需要注意的是 args,這個(gè)是初始化對應(yīng) type 的時(shí)候傳入的構(gòu)造參數(shù),我們先看一下 SpringFactoriesLoader#loadFactoryNames 方法:
首先是會先檢查緩存,假如緩存中存在就直接返回,假如沒有就調(diào)用 classLoader#getResources 方法,傳入 META-INF/spring.factories ,即獲取所有 jar 包下的對應(yīng)文件,并封裝成 UrlResource ,然后使用 PropertiesLoaderUtils 將這些信息讀取成一個(gè)對一對的 properties,我們觀察一下 spring.factories 都是按 properties 格式排版的,假如有多個(gè)就用逗號隔開,所以這里還需要將逗號的多個(gè)類分隔開來,并加到 result 中,由于 result 是一個(gè) LinkedMultiValueMap 類型,支持多個(gè)值插入,最后放回緩存中。最終完成加載 META-INF/spring.factories 中的配置,如下:
我們可以看一下我們找到的 initializer 有多少個(gè):
在獲取到所有的 Initializer 后接下來是調(diào)用 createSpringFactoriesInstances 方法進(jìn)行初始化。
這里的 names 就是我們上面通過類加載器加載到的類名,到這里會先通過反射生成 class 對象,然后判斷該類是否繼承與 ApplicationContextInitializer ,最后通過發(fā)射的方式獲取這個(gè)類的構(gòu)造方法,并調(diào)用該構(gòu)造方法,傳入已經(jīng)定義好的構(gòu)造參數(shù),對于 ApplicationContextInitializer 是無參的構(gòu)造方法,然后初始化實(shí)例并返回,回到原來的方法,這里會先對所有的 ApplicationContextInitializer 進(jìn)行排序,調(diào)用 AnnotationAwareOrderComparator#sort(instances) 方法,這里就是根據(jù) @Order 中的順序進(jìn)行排序。
接下來是設(shè)置 ApplicationListener ,我們跟進(jìn)去就會發(fā)現(xiàn)這里和上面獲取 ApplicationContextInitializer 的方法如出一轍,最終會加載到如圖的 15 個(gè) listener (這里除了 EnableEncryptablePropertiesBeanFactoryPostProcessor 外,其他都是 SpringBoot 內(nèi)部的 Listener):
在完成 SpringApplication 對象的初始化后,我們進(jìn)入了他的 run 方法,這個(gè)方法幾乎涵蓋了 SpringBoot 生命周期的所有內(nèi)容,主要分為九個(gè)步驟,每一個(gè)步驟這里都使用注解進(jìn)行標(biāo)識:
主要步驟如下:
第一步:獲取 SpringApplicationRunListener, 然后調(diào)用他的 staring 方法啟動監(jiān)聽器。
第二步:根據(jù) SpringApplicationRunListeners以及參數(shù)來準(zhǔn)備環(huán)境。
第三步:創(chuàng)建 Spring 容器。
第四步:Spring 容器的前置處理。
第五步:刷新 Spring 容器。
第六步: Spring 容器的后置處理器。
第七步:通知所有 listener 結(jié)束啟動。
第八步:調(diào)用所有 runner 的 run 方法。
第九步:通知所有 listener running 事件。
我們接下來一一講解這些內(nèi)容。
我們首先看一下第一步,獲取 SpringApplicationRunListener :
這里和上面獲取 initializer 和 listener 的方式基本一致,都是通過 getSpringFactoriesInstances , 最終只找到一個(gè)類就是: org.springframework.boot.context.event.EventPublishingRunListener ,然后調(diào)用其構(gòu)造方法并傳入產(chǎn)生 args , 和 SpringApplication 本身:
我們先看一下構(gòu)造函數(shù),首先將我們獲取到的 ApplicationListener 集合添加到initialMulticaster 中, 最后都是通過操作 SimpleApplicationEventMulticaster 來進(jìn)行廣播,我,他繼承于 AbstractApplicationEventMulticaster ,我們先看一下他的 addApplicationListener 方法:
我們可以看出,最后是放到了 applicationListenters 這個(gè)容器中。他是 defaultRetriever 的成員屬性, defaultRetriever 則是 AbstractApplicationEventMulticaster 的私有類,我們簡單看一下這個(gè)類:
我們只需要看一下這里的 getApplicationListeners 方法,它主要是到 beanFactory 中檢查是否存在多的 ApplicationListener 和舊的 applicationListeners 組合并返回,接著執(zhí)行 listener 的 start 方法,最后也是調(diào)用了 AbstractApplicationEventMulticaster 的 multicastEvent 查找支持對應(yīng)的 ApplicationEvent 類型的通知的 ApplicationListener 的 onApplicationEvent 方法 ,這里除了會:
篩選的方法如下,都是調(diào)用了對應(yīng)類型的 supportsEventType 方法 :
如圖,我們可以看到對 org.springframework.boot.context.event.ApplicationStartingEvent 感興趣的有5個(gè) Listener
環(huán)境準(zhǔn)備的具體方法如下:
首先是調(diào)用 getOrCreateEnvironment 方法來創(chuàng)建 environment ,我們跟進(jìn)去可以發(fā)現(xiàn)這里是根據(jù)我們上面設(shè)置的環(huán)境的類型來進(jìn)行選擇的,當(dāng)前環(huán)境會創(chuàng)建 StandardServletEnvironment
我們先來看一下 StandardServletEnvironment 的類繼承關(guān)系圖,我們可以看出他是繼承了 AbstractEnvironment :
他會調(diào)用子類的 customizePropertySources 方法實(shí)現(xiàn),首先是 StandardServletEnvironment 的實(shí)現(xiàn)如下,他會添加 servletConfigInitParams , servletContextInitParams , jndiProperties 三種 properties,當(dāng)前調(diào)試環(huán)境沒有配置 jndi properties,所以這里不會添加。接著調(diào)用父類的 customizePropertySources 方法,即調(diào)用到了 StandardEnvironment 。
我們看一下 StandardEnvironment#customizePropertySources 方法,與上面的三個(gè) properties 創(chuàng)建不同,這兩個(gè)是會進(jìn)行賦值的,包括系統(tǒng)環(huán)境變量放入 systemEnvironment 中,jvm 先關(guān)參數(shù)放到 systemProperties 中:
這里會添加 systemEnvironment 和 systemProperties 這兩個(gè) properties,最終拿到的 properties 數(shù)量如下 4個(gè):
在創(chuàng)建完成 Environment 后,接下來就到了調(diào)用 configureEnvironment 方法:
我們先看一下 configurePropertySources 方法,這里主要分兩部分,首先是查詢當(dāng)前是否存在 defaultProperties ,假如不為空就會添加到 environment 的 propertySources 中,接著是處理命令行參數(shù),將命令行參數(shù)作為一個(gè) CompositePropertySource 或則 SimpleCommandLinePropertySource 添加到 environment 的 propertySources 里面,
接著調(diào)用 ConfigurationPropertySources#attach 方法,他會先去 environment 中查找 configurationProperties , 假如尋找到了,先檢查 configurationProperties 和當(dāng)前 environment 是否匹配,假如不相等,就先去除,最后添加 configurationProperties 并將其 sources 屬性設(shè)置進(jìn)去。
回到我們的 prepareEnvironment 邏輯,下一步是通知觀察者,發(fā)送 ApplicationEnvironmentPreparedEvent 事件,調(diào)用的是 SpringApplicationRunListeners#environmentPrepared 方法,最終回到了 SimpleApplicationEventMulticaster#multicastEvent 方法,我們通過 debug 找到最后對這個(gè)時(shí)間感興趣的 Listener 如下:
其主要邏輯如下:
這個(gè)方法最后加載了 PropertySourceLoader , 這里主要是兩種,一個(gè)是用于 Properties 的,一個(gè)是用于 YAML 的如下:
其中 apply 方法主要是加載 defaultProperties ,假如已經(jīng)存在,就進(jìn)行替換,而替換的目標(biāo) PropertySource 就是 load 這里最后的一個(gè) consumer 函數(shù)加載出來的,這里列一下主要做的事情:
1、加載系統(tǒng)中設(shè)置的所有的 Profile 。
2、遍歷所有的 Profile ,假如是默認(rèn)的 Profile , 就將這個(gè) Profile 加到 environment 中。
3、調(diào)用load 方法,加載配置,我們深入看一下這個(gè)方法:
他會先調(diào)用 getSearchLocations 方法,加載所有的需要加載的路徑,最終有如下路徑:
其核心方法是遍歷所有的 propertySourceLoader ,也就是上面加載到兩種 propertySourceLoader ,最紅 loadForFileExtension 方法,加載配置文件,這里就不展開分析了,說一下主要的作用,因?yàn)槊總€(gè) propertySourceLoader 都有自己可以加載的擴(kuò)展名,默認(rèn)擴(kuò)展名有如下四個(gè) properties, xml, yml, yaml,所以最終拿到文件名字,然后通過 - 拼接所有的真實(shí)的名字,然后加上路徑一起加載。
接下來,我們分析 BackgroundPreinitializer ,這個(gè)方法在接收 ApplicationPrepareEnvironment 事件的時(shí)候真正調(diào)用了這份方法:
1、 ConversionServiceInitializer 主要負(fù)責(zé)將包括 日期,貨幣等一些默認(rèn)的轉(zhuǎn)換器注冊到 formatterRegistry 中。
2、 ValidationInitializer 創(chuàng)建 validation 的匹配器。
3、 MessageConverterInitializer 主要是添加了一些 http 的 Message Converter。
4、 JacksonInitializer 主要用于生成 xml 轉(zhuǎn)換器的。
接著回到我們將的主體方法, prepareEnvironment 在調(diào)用完成 listeners.environmentPrepared(environment) 方法后,調(diào)用 bindToSpringApplication(environment) 方法,將 environment 綁定到 SpirngApplication 中。
接著將 enviroment 轉(zhuǎn)化為 StandardEnvironment 對象。
最后將 configurationProperties 加入到 enviroment 中, configurationProperties 其實(shí)是將 environment 中其他的 PropertySource 重新包裝了一遍,并放到 environment 中,這里主要的作用是方便 PropertySourcesPropertyResolver 進(jìn)行解析。
它主要是檢查是否存在 spring.beaninfo.ignore 配置,這個(gè)配置的主要作用是設(shè)置 javaBean 的內(nèi)省模式,所謂內(nèi)省就是應(yīng)用程序在 Runtime 的時(shí)候能檢查對象類型的能力,通常也可以稱作運(yùn)行時(shí)類型檢查,區(qū)別于反射主要用于修改類屬性,內(nèi)省主要用戶獲取類屬性。那么我們什么時(shí)候會使用到內(nèi)省呢,java主要是通過內(nèi)省工具 Introspector 來完成內(nèi)省的工作,內(nèi)省的結(jié)果通過一個(gè) Beaninfo 對象返回,主要包括類的一些相關(guān)信息,而在 Spring中,主要是 BeanUtils#copyProperties 會使用到,Spring 對內(nèi)省機(jī)制還進(jìn)行了改進(jìn),有三種內(nèi)省模式,如下圖中紅色框框的內(nèi)容,默認(rèn)情況下是使用 USE_ALL_BEANINFO。假如設(shè)置為true,就是改成第三中 IGNORE_ALL_BEANINFO
首先是檢查 Application的類型,然后獲取對應(yīng)的 ApplicationContext 類,我們這里是獲取到了 org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext 接著調(diào)用 BeanUtils.instantiateClass(contextClass); 方法進(jìn)行對象的初始化。
最終其實(shí)是調(diào)用了 AnnotationConfigServletWebServerApplicationContext 的默認(rèn)構(gòu)造方法。我們看一下這個(gè)方法做了什么事情。這里只是簡單的設(shè)置了一個(gè) reader 和一個(gè) scanner,作用于 bean 的掃描工作。
我們再來看一下這個(gè)類的繼承關(guān)系
這里獲取 ExceptionReporter 的方式主要還是和之前 Listener 的方式一致,通過 getSpringFactoriesInstances 來獲取所有的 SpringBootExceptionReporter 。
其主要方法執(zhí)行如下:
關(guān)于去水印小程序源碼springboot和去水印小程序源碼個(gè)人的介紹到此就結(jié)束了,不知道你從中找到你需要的信息了嗎 ?如果你還想了解更多這方面的信息,記得收藏關(guān)注本站。
掃描二維碼推送至手機(jī)訪問。
版權(quán)聲明:本文由飛速云SEO網(wǎng)絡(luò)優(yōu)化推廣發(fā)布,如需轉(zhuǎn)載請注明出處。