我真不信,這年頭還有人能懂SpringBoot的ClassLoader加載機(jī)制
SpringBoot的ClassLoader加載機(jī)制
在Spring Boot的嵌入式Web容器原理一節(jié)中,我們已經(jīng)介紹了Spring Boot對Tomcat容器的加載過程,本節(jié)我們進(jìn)一步講解SpringBoot的ClassLoader加載機(jī)制。
熟悉Tomcat工作原理的人應(yīng)該知道,Tomcat內(nèi)部實(shí)現(xiàn)了自定義的類加載器,打破了Java的雙親委派機(jī)制,下面我們先看看什么是雙親委派機(jī)制。

雙親委派機(jī)制
雙親委派機(jī)制是指Java的類加載器收到一個(gè)類加載請求時(shí),該類加載器首先會(huì)把請求委派給父類加載器。每個(gè)類加載器都是如此,只有當(dāng)父類加載器在自己的搜索范圍內(nèi)找不到指定類時(shí),子類加載器才會(huì)嘗試自己去加載。Java類加載機(jī)制如下圖所示。

我們通常將類加載器分為下面的三種類型。
● 啟 動(dòng) 類 加 載 器 ( Bootstrap ClassLoader ) :加 載jre/lib/rt.jar。
● 擴(kuò) 展 類 加 載 器 ( Extension ClassLoader ) :加 載jre/lib/ext/*.jar。
● 應(yīng) 用 程 序 類 加 載 器 ( Application ClassLoader ) :加 載classpath上指定的類庫。
如果使用JDK默認(rèn)的雙親委派模式,Tomcat的類加載器可以加載嗎?我們思考一下Tomcat作為一個(gè)Web容器的使用場景。
在Web容器中,可能同時(shí)需要部署兩個(gè)以上的應(yīng)用程序。一個(gè)典型的場景是不同的應(yīng)用程序會(huì)依賴同一個(gè)第三方類庫的不同版本,不能要求同一個(gè)類庫在同一個(gè)服務(wù)器中只有一份,因此要保證每個(gè)應(yīng)用程序的類庫都是獨(dú)立的,保證相互隔離。
Tomcat如果使用默認(rèn)類加載器,是無法加載兩個(gè)相同類庫的不同版本的。所以Tomcat團(tuán)隊(duì)設(shè)計(jì)了自己獨(dú)特的類加載機(jī)制,解決上面的應(yīng)用jar包沖突等問題,通過自定義的類加載機(jī)制可以完美地解決Tomcat容器中不同應(yīng)用的隔離問題。下面我們看看Tomcat的類加載機(jī)制圖和JDK默認(rèn)的加載機(jī)制圖的區(qū)別,如下圖所示。

其中:
● Common ClassLoader:Tomcat最基本的類加載器,加載路徑中的Class可以被Tomcat容器本身及各個(gè)WebApp訪問。
● Catalina ClassLoader:Tomcat容器私有的類加載器,加載路徑中的Class對于WebApp不可見。
● Shared ClassLoader:各個(gè)WebApp共享的類加載器,加載路徑中的Class對所有WebApp可見,但是對于Tomcat容器不可見。
● WebApp ClassLoader:各個(gè)WebApp私有的類加載器,加載路徑中的Class只對當(dāng)前WebApp可見,各個(gè)項(xiàng)目就是通過各自的WebApp ClassLoader加載進(jìn)入Tomcat容器的。探索Spring Boot的ClassLoaderSpring Boot的內(nèi)置Tomcat是如何加載到我們的項(xiàng)目中的呢?我們還是從SpringApplication的run方法開始追溯Tomcat啟動(dòng)Web Server的過程,ApplicationContext執(zhí)行刷新操作并創(chuàng)建嵌入式容器,源碼如下:


然后,進(jìn)入EmbeddedServletContainer的
getEmbeddedServletContainer方法,它會(huì)初始化Tomcat實(shí)例并準(zhǔn)備Context。

最后,跟進(jìn)prepareContext方法,我們就可以看到嵌入式Tomcat的類加載方式,源碼如下:


可 見 , Spring Boot 以 啟 動(dòng) 線 程 的 Context ClassLoader 作 為Tomcat的WebApp ClassLoader的父類加載器,而Tomcat的WebApp類加載器使用
TomcatEmbeddedWebAppClassLoader。所以整個(gè)項(xiàng)目的jar包的加載都是由Spring Boot的主線程Context ClassLoader完成的,于是Context ClassLoader就可以訪問我們的Web容器下的所有資源了。
需要說明的是,Spring Boot使用了FatJar技術(shù)將所有依賴放在一個(gè)最終的jar包文件BOOT-INF/lib中,它可以把當(dāng)前項(xiàng)目的Class全部放在BOOT-INF/classes目錄中。你可以在Spring Boot的工程項(xiàng)目中看到,在pom.xml文件中引入了如下依賴:

jar包目錄結(jié)構(gòu)如下:

從這個(gè)目錄結(jié)構(gòu)中,你可以看到Tomcat的啟動(dòng)包(tomcat-embedcore-8.5.29.jar)就在Lib目錄下。而FatJar的啟動(dòng)Main函數(shù)就是JarLauncher,它負(fù)責(zé)創(chuàng)建LaunchedURLClassLoader來加載/lib下面的所有jar包。下面是Spring Boot應(yīng)用的Manifest文件內(nèi)容。

這里的Main-Class是
org.springframework.boot.loader.JarLauncher,它是這個(gè)jar包啟動(dòng)的Main函數(shù)。
還有一個(gè)Start-Class:
com.test.demo.SpringbootDemoApplication,它是應(yīng)用自己的Main函數(shù) 。Spring Boot 將 jar 包 中 的 Main-Class 進(jìn) 行 了 替 換 , 換 成 了JarLauncher,并增加了一個(gè)Start-Class參數(shù),這個(gè)參數(shù)對應(yīng)的類才是真正的業(yè)務(wù)Main函數(shù)入口。我們再看看這個(gè)JarLaucher具體干了什么,源碼如下:

launch方法分為三步:(1)注冊URL協(xié)議并清除應(yīng)用緩存。
(2)設(shè)置類加載路徑。
(3)執(zhí)行main方法。
這里面,Spring Boot自定義的ClassLoader能夠識別FatJar中的資源,包括:在指定目錄下的項(xiàng)目編譯Class、在指定目錄下的項(xiàng)目依賴jar包。Spring Boot支持多個(gè)!/分隔符,通過自行實(shí)現(xiàn)的ZipFile解析器實(shí)現(xiàn)了對URL插入的定制化Handler,將獲取的URL數(shù)據(jù)作為參數(shù)傳遞給自定義的URLClassLoader,最終實(shí)現(xiàn)資源的獲取和解析。
綜上,在傳統(tǒng)的以Tomcat容器部署War包項(xiàng)目中,我們的Web項(xiàng)目其實(shí)是一個(gè)被加載對象。Tomcat容器作為主線程的父類加載器來加載不同的應(yīng)用,Tomcat獨(dú)特的WebApp ClassLoader各自加載不同目錄下的War包應(yīng)用,應(yīng)用之間使用ClassLoader實(shí)現(xiàn)了很好的隔離。
Spring Boot主要通過實(shí)例化SpringApplication來啟動(dòng)應(yīng)用,內(nèi)置的Tomcat容器實(shí)現(xiàn)相關(guān)Web環(huán)境及初始化資源準(zhǔn)備,并將Tomcat內(nèi)嵌的WebApp ClassLoader作為子ClassLoader掛載到Spring Boot的主線程 Context ClassLoader 。同 時(shí) , Spring Boot 中 的 @Controller 、@RequestMapping 等 Web 服 務(wù) 資 源 通 過 自 動(dòng) 裝 配 機(jī) 制 , 在SpringApplication啟動(dòng)過程中通過掃描將資源對象加載到Spring IoC容器中。最后Spring Boot使用FatJar自定義的jar包壓縮和加載機(jī)制,規(guī)范了Spring Boot項(xiàng)目的包及目錄結(jié)構(gòu)。
小結(jié)
目前,基于腳手架(基底)模式進(jìn)行軟件構(gòu)建已經(jīng)成為微服務(wù)架構(gòu)落地的主流開發(fā)方式,可以顯著提升開發(fā)人員的工作效率。SpringBoot本身基于Spring框架,繼承了Spring強(qiáng)大的技術(shù)特性。本章我們對Spring Boot框架的核心模塊和機(jī)制進(jìn)行了剖析,詳細(xì)講解了SpringBoot的自動(dòng)化配置原理、Starter機(jī)制和自定義Starter的工作原理,固化了“約定優(yōu)于配置”和“開箱即用”等簡潔的開發(fā)理念和高效開發(fā)方式。同時(shí),本章也是后續(xù)Spring Cloud微服務(wù)治理的基礎(chǔ),在開始技術(shù)進(jìn)階之前,務(wù)必掌握Spring Boot基礎(chǔ)原理,這樣才能做到事半功倍。
本文給大家講解的內(nèi)容是Spring Boot的ClassLoader加載機(jī)制
下篇文章給大家詳細(xì)講解服務(wù)注冊中心,歡迎大家來學(xué)習(xí)
覺得文章不錯(cuò)的朋友可以轉(zhuǎn)發(fā)此文關(guān)注小編;
感謝大家的支持!
本文就是愿天堂沒有BUG給大家分享的內(nèi)容,大家有收獲的話可以分享下,想學(xué)習(xí)更多的話可以到微信公眾號里找我,我等你哦。
