03-Spring初級(jí)容器初始化:Resource到底是個(gè)什么玩意兒?

開篇
上一節(jié),我們了解了Spring基礎(chǔ)容器XmlBeanFactory是如何使用的,并且分析了一下Spring容器最基本的原理。
從這一節(jié)開始呢,我們就要準(zhǔn)備進(jìn)入到Spring源碼的分析環(huán)節(jié)了,雖然Spring源碼看上去像一個(gè)黑色森林始終見不到底一樣,但是,如果我們能沿著Spring源碼的一條主線分析,也許會(huì)明朗許多,而第一條主線就是Spring容器的初始化。
我們先從配置文件applicationContext.xml的加載入手,開始Spring容器初始化的分析,這一節(jié)總體的思路如下:
1.從ClassPathResource加載applicationContext.xml開始,了解下ClassPathResource是什么,ClassPathResource對(duì)應(yīng)的類繼承體系又是怎樣的。
2.在ClassPathResource類繼承體系中,Resource接口和InputStreamSource接口中都有哪些東西呢?這樣設(shè)計(jì)的好處是什么呢?
3.最后,我們?cè)賮砜聪鲁R姷囊恍㏑esource接口實(shí)現(xiàn)類,在底層一般是如何加載資源的呢?
初識(shí)ClassPathResource
我們先來看下上一節(jié)demo的代碼:

可以看到,一開始Spring配置文件applicationContext.xml就被封裝成了ClassPathResource,那ClassPathResource是什么呢?
要了解ClassPathResource是什么,我們先來看下它的類繼承圖:

可以看到,ClassPathResource其實(shí)就是一個(gè)實(shí)現(xiàn)了Resource接口的實(shí)現(xiàn)類而已,和ClassPathResource類似的還有UrlResource、FileSystemResource、ByteArrayResource和InputStreamResource,它們都實(shí)現(xiàn)了Resource接口,而Resource接口又實(shí)現(xiàn)了InputStreamSource接口。
那我們?nèi)滩蛔【鸵獑柫?,Resource接口是什么的呢?InputStreamSource接口又是什么呢?各種Resource的實(shí)現(xiàn)類之間又有什么區(qū)別呢?接下來,我們一點(diǎn)點(diǎn)來看下。
Resource和InputStreamSource
首先,我們先來回答第一個(gè)問題,也就是什么是Resource接口,Spring統(tǒng)一把所有使用到的資源都抽象成了Resource,不同來源的資源對(duì)應(yīng)著不同的Resource實(shí)現(xiàn)類。
我們可以來看一下Resource接口中,都有哪些方法:

可以看到,在Resource接口中,首先它定義了對(duì)資源狀態(tài)判斷的方法,如exists方法判斷資源是否存在、isFile方法判斷資源是否是文件類型的、isOpen方法判斷資源是否處于打開的狀態(tài)、isReadable方法判斷資源是否是可讀狀態(tài)的。
而且,Resource接口還分別提供了資源到File、URI以及URL的轉(zhuǎn)換方法,如getFile方法可以將資源轉(zhuǎn)換為File,getURI方法可以將資源轉(zhuǎn)換為URI,getURL方法可以將資源轉(zhuǎn)換為URL。
Resource接口還提供了獲取文件名稱的getFilename方法、獲取資源的描述信息的getDescription方法,getDescription方法一般它可以用于日志信息的打印。
我們?cè)倏聪碌诙€(gè)問題,InputStreamSource接口是什么呢?我們來看下接口InputStreamSource:

可以看到,InputStreamSource接口中只有一個(gè)方法getInputStream,而且方法返回的就是一個(gè)輸入流InputStream。
從前面的類繼承圖中我們也看到了,Resource是繼承了InputStreamSource接口的,所以,這也就意味著所有資源只要封裝成了Resource,就可以通過調(diào)用InputStreamSource中的getInpustream方法,獲取資源對(duì)應(yīng)的輸入流InputStream了。
而資源的來源又是多種多樣的,比如,我們?cè)赿emo中的applicationContext.xml,則是存放在classpath路徑下的xml文件,ClassPathResource就是用來加載classpath路徑下的資源文件的。
而UrlResource、FileSystemResource、ByteArrayResource和InputStreamResource,它們分別是加載URL、文件、Byte數(shù)組和InputStream的Resource實(shí)現(xiàn)類。
各種Resource是如何加載資源的呢?
我們?cè)賮砜聪伦詈笠粋€(gè)問題,各種Resource的實(shí)現(xiàn)類之間的區(qū)別是什么呢?我們?cè)賮砘仡櫹翿esource的類繼承圖:

可以看到,不同的Resource接口實(shí)現(xiàn)類都實(shí)現(xiàn)了Resource接口,而Resource接口的那些方法我們剛才也看到了,其實(shí)就是一些方便我們對(duì)資源操作而已,這些Resource實(shí)現(xiàn)類的區(qū)別主要在于它們的資源來源,以及相應(yīng)的加載方式。
我們可以很容易的想到,因?yàn)镽esource接口繼承了InputStreamSource接口,所以不管你是什么類型的資源,一旦封裝成Resource類,都可以通過調(diào)用InputStreamSource中的getInputStream方法,獲取對(duì)應(yīng)資源的輸入流InputStream,而我們一旦獲取到了資源的輸入流,再進(jìn)行正常的開發(fā)就容易的多了。
所以,通過觀察獲取輸入流的方式,我們或許可以知道不同的Resource實(shí)現(xiàn)類是如何加載資源的,我們先來看下ClassPathResource中的getInputStream方法,如下圖所示:

可以看到,ClassPathResource中的getInputStream方法加載資源的方式比較簡(jiǎn)單,就是通過class或classLoader的底層方法來加載的,沒什么特別的地方。
再來看下FileSystemResource:

更簡(jiǎn)單無非就是通過JDK底層方法,根據(jù)文件的路徑去加載資源的InputStream。
而UrlResource、ByteArrayResource和InputStreamResource是如何獲取相應(yīng)的InputStream,想必我們應(yīng)該都能猜到了,我們可以分別來看下:



可以看到,UrlResource通過連接對(duì)應(yīng)URL的去獲取InputStream,ByteArrayResource通過Byte類型數(shù)組去獲取對(duì)應(yīng)的InputStream,而InputStreamResource本身就是包含InputStream的,直接返回它就可以了。
總結(jié)
好了,今天的知識(shí)點(diǎn),我們就講到這里了,我們來總結(jié)一下吧。
通過本節(jié)的學(xué)習(xí),我們了解了Resource的繼承體系,并且知道了Resource就是Spring內(nèi)部對(duì)各種資源的一個(gè)抽象,而InputStreamResource接口的實(shí)現(xiàn),使得我們對(duì)各種來源的資源都可以輕松獲取對(duì)應(yīng)的輸入流InputStream。
然后,我們也看了下各個(gè)Resource實(shí)現(xiàn)類獲取InputStream的源碼,可以看到它們加載資源的方式都是不同的,下一節(jié)我們繼續(xù)來看下,Spring容器獲取到資源Resource之后會(huì)如何處理。