開發與維運

Spring 5 中文解析核心篇-IoC容器之Resource

這個章節涵蓋了Spring怎樣處理和在Spring中使用資源文件。包括下面主題:

2.1 介紹

Java的標準java.net.URL類和標準處理URL前綴變體,不幸地,對於所有訪問低級資源的能力還不夠。例如,這裡沒有需要從類路徑或相關聯的ServletContext獲取資源使用的標準URL實現。當然也可以註冊新的處理器為特定URL前綴(類似已經存在的前置處理器,例如:http:),通常這是十分的複雜,並且URL接口仍然缺乏一些功能描述,例如一種檢查所指向資源是否存在的方法。

2.2 Resource接口

Spring的Resource接口意思是為抽象獲取低級別資源提供更多的能力。下面清單顯示了Resource接口定義:

public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isOpen();

    URL getURL() throws IOException;

    File getFile() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();
}

Resource接口定義顯示這樣,它拓展了InputStreamSource接口。下面清單顯示InputStreamSource接口的定義:

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;
}

一些Resource接口中重要的方法:

  • getInputStream():定位和打開資源並且返回從資源中讀取的InputStream。每次調用都返回一個新的InputStream。我們有責任去關閉流。
  • exists():任何一個boolean指示是否這個資源在物理路徑存在。
  • isOpen():然後一個boolean指示此資源是否代表打開流的句柄。如果為trueInputStream不能被多次讀取且只能讀取一次,而且需要關閉資源避免被洩露。對於所有常規資源實現,返回false,但InputStreamResource除外。
  • getDescription():返回這個資源的描述,以在使用該資源時用於錯誤輸出。這通常是標準文件名或資源的實際URL。

其他方法允許你獲取一個真實URL或FIle對象描述資源(如果底層實現是兼容和支持該功能)。

當需要資源時,Spring自身廣泛地使用Resource抽象,在許多方法簽名上參數類型。一些Spring API中的其他方法(例如,各種ApplicationContext實現的構造函數)採用String形式,該字符串以未經修飾或簡單的形式用於創建適合該上下文實現的Resource,或者通過String路徑上的特殊前綴,讓調用者指定必須創建並使用特定的資源實現。

雖然Resource接口在Spring中被大量使用,但是在你自己的代碼中作為一個通用的實用程序類來使用它實際上是非常有用的,以便訪問資源,即使你的代碼不知道或不關心Spring的任何其他部分。雖然這個耦合Spring到你的代碼,它僅僅耦合非常小的工具類集合,可以用作URL的更強大替代,並且可以認為與你將用於此目的的任何其他庫等效。

Resource抽象不能替換功能。它儘可能地包裝它。例如,UrlResource包裝URL和使用包裝的URL去工作。

2.3 內建的Resource實現

Spring包含下面的Resource實現:

2.3.1 UrlResource

UrlResource包裝了java.net.URL,可用於訪問通常可以通過URL訪問的任何對象,例如文件,HTTP目標、FTP目標等。所有URL有一個標準化的String表示,因此使用適當的標準化前綴來指示一種URL類型。這包括獲取文件系統路徑file:、通過HTTP協議獲取資源http:、通過FTP獲取資源ftp:等等。

UrlResource是由Java代碼通過顯式使用UrlResource構造函數創建的,但通常在調用帶有String參數表示路徑的API方法時隱式創建。對於後一種情況,JavaBean PropertyEditor最終決定哪一種Resource類型被創建。如果字符串包含我們所知的前綴(例如 classpath:),它創建一個適當的指定前綴的Resource。然而,如果不能識別前綴,假設字符串是標準的URL字符串並創建一個UrlResource

2.3.2 ClassPathResource

這個類代表一個能夠從類路徑獲取的資源。它使用線程上下文類加載器、給定的類加載器或給定的類來加載資源。

這個資源實現支持將解析作為java.io.File。如果類路徑資源駐留在文件系統中,而不是類路徑資源駐留在jar中,並且沒有(由servlet引擎或其他環境)擴展到文件系統。為了解決這個問題,各種Resource實現始終支持將解析作為java.net.URL進行。

當你採用一個String參數描述路徑調用API方法時,ClassPathResource是通過顯示使用ClassPathResource構造函數通過Java代碼創建,但是通常隱式地被創建。對於後一種情況,javabean PropertyEditor識別在字符串路徑中指定前綴classpath:並且在這個場景中創建一個ClassPathResource

2.3.3 FileSystemResource

這是java.io.Filejava.nio.file.Path句柄的Resource實現。它支持解析作為File和URL。

2.3.4 ServletContextResource

這是ServletContext資源的Resource實現,它解釋相關Web應用程序根目錄中的相對路徑。

它始終支持流訪問和URL訪問,但僅在擴展Web應用程序實現被擴展和資源在實際文件系統上時才允許java.io.File訪問。它是在文件系統上擴展還是直接擴展,或者直接從JAR或其他類似數據庫(可以想到的)中訪問,實際上取決於Servlet容器。

2.3.5 InputStreamResource

InputStreamResource是一個給定InputStreamResource實現。僅當沒有特定的資源實現時才應使用它。特別是,在可能的情況下,最好選擇ByteArrayResource或任何基於文件的Resource實現。

對比其他的Resource實現,這是一個對於已經打開的資源的描述。因此,isOpen()方法返回true。如果你需要將資源描述符保留在某個地方或需要多次讀取流,請不要使用它。

2.3.6 ByteArrayResource

這是一個給定byte數組的Resource實現。它為給定byte數組創建一個ByteArrayInputStream

這對於從任何給定的字節數組加載內容很有用,而不必求助於一次性InputStreamResource

參考代碼:com.liyong.ioccontainer.starter.ResourceIocContainer

2.4 ResourceLoader

ResourceLoader接口是由可以返回(即加載)資源實例的對象實現的。下面清單顯示ResourceLoader定義:

public interface ResourceLoader {

    Resource getResource(String location);
}

所有的應用上下文實現ResourceLoader接口。因此,所有應用上下文可以使用去獲取Resource實例。

當你在指定應用上下文上調用getResource(),並且定義路徑沒有指定前綴,你可以獲取特定應用上下文中適合的Resource類型。例如,假定針對ClassPathXmlApplicationContext實例執行了以下代碼片段:

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

針對ClassPathXmlApplicationContext,這個代碼返回一個ClassPathResource實例。針對FileSystemXmlApplicationContext如果相同方法被執行,它將返回FileSystemResource。對於WebApplicationContext,它將返回一個ServletContextResource。它將類似地為每一個上下文返回適合的對象。

最後,你可以以適合特定應用程序上下文的方式加載資源。

另一方面,你也可以強制使用ClassPathResource,不管應用上下文類型,通過指定classpath:前綴,類似下面例子顯示:

Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");

類似地,你可以通過指定標準的java.net.URL前綴強制使用UrlResource。下面兩個例子使用filehttp前綴:

Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");

下面表格總結對於轉換String對象到Resource對象的策略 :

Prefix Example Explanation
classpath: classpath:com/myapp/config.xml 類路徑加載
file: file:///data/config.xml 作為URL資源從文件系統加載 FileSystemResourceCaveats.
http: https://myserver/logo.png 作為URL加載
(none) /data/config.xml 依賴底層ApplicationContext
2.5 ResourceLoaderAware接口

ResourceLoaderAware接口是一個特殊的回調接口,它表示希望使用ResourceLoader引用提供的組件。下面清單顯示ResourceLoaderAware接口定義:

public interface ResourceLoaderAware {

   void setResourceLoader(ResourceLoader resourceLoader);
}

當一個類實現ResourceLoaderAware並且被部署到應用上下文中(作為Spring管理的bean),它應用上下文作為ResourceLoaderAware被識別。應用上下文調用setResourceLoader(ResourceLoader),應用自身作為參數(記住,Spring中的所有應用程序上下文都實現ResourceLoader接口)。

因為ApplicationContextResourceLoader,bean也可以實現ApplicationContextAware接口並且使用使用應用上下文直接地加載資源。但是,通常,如果需要的話,最好使用專門的ResourceLoader接口。該代碼將僅耦合到資源加載接口(可以視為實用程序接口),而不耦合到整個Spring ApplicationContext接口。

在應用中的組件,你也可以依賴注入ResourceLoader作為實現ResourceLoaderAware接口替代方案。“傳統”構造函數和byType自動裝配模式(如“自動裝配協作器”中所述)能夠分別為構造函數參數或setter方法參數提供ResourceLoader。為了更大的靈活性(包含自動裝配字段和多參數方法),考慮使用基於註解的特性。在這種情況下,ResourceLoader自動裝配到字段、構造函數或方法參數、字段、構造函數或方法攜帶有@Autowired註解,就會期望ResourceLoader類型。更多消息,查看@Autowired使用。

2.6 依賴Resources

如果Bean本身將通過某種動態過程來確定和提供資源路徑,那麼對於Bean來說,使用ResourceLoader接口加載資源可能是有意義的。例如,考慮加載某種模板,其中所需的特定資源取決於用戶的角色。如果資源是靜態的,那麼完全消除ResourceLoader接口的使用是有意義的,讓bean公開它需要的資源屬性,並期望將它們注入到bean中。

然後注入這些屬性的麻煩之處在於,所有應用程序上下文都註冊並使用了特殊的JavaBeans PropertyEditor,它可以將String路徑轉換為Resource對象。如果myBean有一個Resource類型模版屬性,它可以為資源配置一個簡單的字符串,類似下面例子顯示:

<bean id="myBean" class="...">
    <property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>

注意,資源路徑沒有前綴。因此,由於應用程序上下文本身將被用作ResourceLoader,所以資源本身是通過ClassPathResource、FileSystemResource或ServletContextResource加載的,這取決於上下文的確切類型。

如果你需要強制指定使用的Resource類型,你可以使用前綴。下面兩個例子顯示怎樣去強制使用ClassPathResource和UrlResource(後面這個例子使用文件系統獲取):

<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>

參考代碼:com.liyong.ioccontainer.starter.Resource2IocContainer

2.7 應用上下文和資源路徑

這個章節涵蓋怎樣創建應用上下文與資源,包括與XML一起使用的快捷方式,如何使用通配符以及其他詳細信息

2.7.1 構造應用上下文

應用上下文構造通常地採用字符串或資源位置路徑字符串,例如構成上下文定義的XML文件。

當位置路徑沒有前綴時,從該路徑構建並用於加載Bean定義的特定Resource類型取決於特定應用程序上下文,並且適用於該特定應用程序上下文。例如,考慮下面例子,創建一個ClassPathXmlApplicationContext

ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

bean定義從類路徑被加載,因為ClassPathResource被使用。然而,考慮下面例子,創建一個FileSystemXmlApplicationContext

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/appContext.xml");

bean定義從文件系統位置被加載(在這個場景中,相對於當前工作目錄)。

注意,使用位置路徑上的特殊類路徑前綴或標準URL前綴會覆蓋為加載定義而創建的默認資源類型。

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");

使用FileSystemXmlApplicationContext從類路徑加載bean定義。然而,它仍然是一個FileSystemXmlApplicationContext。如果隨後將其用作ResourceLoader,則任何無前綴的路徑仍將視為文件系統路徑。

構造ClassPathXmlApplicationContext實例-快捷方式

ClassPathXmlApplicationContext暴露許多構造方法去實例化。基本思想是,你只提供一個字符串數組,該字符串數組僅包含XML文件本自身的文件名(不包含前導路徑信息),並且還提供一個Class。然後,ClassPathXmlApplicationContext從提供的類中獲取路徑信息。

考慮下面路徑佈局:

com/
  foo/
    services.xml
    daos.xml
    MessengerService.class

以下示例顯示如何實例化由在名為service.xmldaos.xml(位於類路徑中)的文件中定義的bean組成的ClassPathXmlApplicationContext實例:

ApplicationContext ctx = new ClassPathXmlApplicationContext(
    new String[] {"services.xml", "daos.xml"}, MessengerService.class);

查看ClassPathXmlApplicationContext詳細javadock的各種構造。

2.7.2 應用程序上下文構造函數資源路徑中的通配符

應用程序上下文構造函數值中的資源路徑可以是簡單路徑(如先前所示),每個路徑都具有到目標資源的一對一映射,或者可以包含特殊的“classpath*:”前綴或內部Ant常規樣式的正則表達式(通過使用Spring的PathMatcher進行匹配)。後者都是有效的通配符。

這種機制使用之一是當你需要組件風格封裝應用時。所有組件都可以將上下文定義片段“發佈”到一個已知的位置路徑,並且,當使用以classpath*:作為前綴的相同路徑創建最終的應用程序上下文時,所有組件片段都會被自動獲取。

注意,這種通配符是特定於在應用程序上下文構造函數中使用資源路徑的(或者當你直接使用PathMatcher程序類層次結構時),並在構造時解析。它與資源類型本身無關。你不能使用classpath*:前綴來構造實際的資源,因為一個資源每次只指向一個資源。

Ant風格模式

路徑位置可以包含Ant風格模式,類似下面例子顯示:

/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml

當路徑位置包含一個Ant風格模式時,解析器允許更復雜程序嘗試去解析通配符。它為最後一個非通配符段的路徑生成一個資源,並從中獲得一個URL。如果這個URL不是jar:URL或包含特定變體(例如,在WebLogic中的zip:、在WebSphere中的wsjar等等),從中獲得一個java.io.File,並通過遍歷文件系統來解析通配符。對於jar URL,解析器可以從中獲取java.net.Jar URLConnection,也可以手動解析jar URL,然後遍歷jar文件的內容以解析通配符。

影響可移植性

如果指定的路徑已經是一個文件URL(由於基本ResourceLoader是一個文件系統,所以它是隱式的,或者是顯式的),則保證通配符可以完全可移植的方式工作。

如果指定的路徑是類路徑位置,則解析器必須通過調用Classloader.getResource()獲得最後的非通配符路徑段URL。由於這只是路徑的一個節點(而不是末尾的文件),因此實際上(在ClassLoader javadoc中)未定義確切返回的是哪種URL。。在實踐中,它總是一個java.io.File描述目錄(類路徑資源解析為文件系統的位置)或一個一些種類(類路徑資源解析為jar位置)jar URL。儘管如此,此操作仍存在可移植性問題。

如果為最後一個非通配符段獲取了jar URL,則解析器必須能夠從中獲取java.net.Jar URLConnection或手動解析jar URL,以便能夠遍歷jar的內容並解析通配符。這在大多數環境中確實有效,但在其他環境中則無效,因此我們強烈建議在依賴特定環境之前,對來自jars的資源的通配符解析進行徹底測試。

classpath*:前綴

當構造基於XML應用上下文時,位置字符串可能使用指定的classpath*:前綴,類似下面例子顯示:

ApplicationContext ctx =
    new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");

這個特殊的前綴指定必須獲取與給定名稱匹配的所有類路徑資源(內部,實際上是通過調用ClassLoader.getResources(…)發生的),然後合併形成最終的應用程序上下文定義。

通配符類路徑依賴類加載器底層的getResources()方法。由於當今大多數應用程序服務器都提供自己的類加載器實現,因此行為可能有所不同,尤其是在處理jar文件時。檢查classpath *是否有效的一個簡單測試是使用classloaderclasspath的jar中加載文件:

getClass().getClassLoader().getResources("<someFileInsideTheJar>")。嘗試對具有相同名稱但位於兩個不同位置的文件進行此測試。如果返回了不合適的結果,請檢查應用程序服務器文檔中可能會影響類加載器行為的設置。

你可以在其餘的位置路徑中將classpath*:前綴與PathMatcher模式結合使用(例如,classpath*:META-INF/*-beans.xml),在這個場景中,解析策略是相當地簡單:在最後一個非通配符路徑段上使用ClassLoader.getResources()調用,以獲取類加載器層次結構中的所有匹配資源,然後在每個資源之外,對通配符子路徑使用前面所述的相同PathMatcher解析策略。

通配符相關的其他注意

注意,當與ant樣式模式結合使用時,除非實際目標文件位於文件系統中,否則classpath*:只能在模式啟動之前可靠地與至少一個根目錄一起工作。這意味著諸如classpath * : * .xml之類的模式可能不會從jar文件的根目錄檢索文件,而只會從擴展目錄的根目錄檢索文件。

Spring檢索類路徑條目的能力源於JDK的ClassLoader.getResources()方法,該方法返回文件系統中的空字符串位置(表示可能要搜索的根)。Spring還會評估jar文件中的URLClassLoader運行時配置和java.class.path清單,但這不能保證會導致可移植行為。

類路徑包掃描要求在類路徑中對於目錄條目存在。使用Ant構建JAR時,請勿激活JAR任務的文件開關。另外,在某些環境中,基於安全策略,類路徑目錄可能不會公開。例如,JDK 1.7.0_45及更高版本上的獨立應用程序(這需要在清單中設置“受信任的庫”)查看https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources

在JDK9的模塊路徑上,Spring的類路徑掃描通常可以正常進行。強烈建議在此處將資源放入專用目錄,以避免在搜索jar文件根目錄級別時出現上述可移植性問題。

具有classpath:的Ant樣式模式:如果要搜索的根包在多個類路徑位置可用,不能保證找到匹配的資源。考慮下面資源為主例子:

com/mycompany/package1/service-context.xml

現在考慮一個ant樣式的路徑,有人可能會使用它來嘗試查找該文件Ant格式路徑

classpath:com/mycompany/**/service-context.xml

這些資源可能只在一個位置,但是當使用諸如前面示例的路徑嘗試對其進行解析時,解析器將處理getResource( "com/mycompany”)返回的(第一個)URL。如果這個基礎包節點在多個類加載器路徑中存在,實際的最終資源可能不存在。因此,在這種情況下,你應該首選使用具有相同Ant樣式模式的classpath *:,該模式將搜索包含根包的所有類路徑位置。

2.7.3 FileSystemResource注意事項

未附加到FileSystemApplicationContextFileSystemResource(即,當FileSystemApplicationContext不是實際的ResourceLoader時)將按你期望的方式處理絕對路徑和相對路徑。相對路徑是相對於當前工作目錄的,而絕對路徑是相對於文件系統的根的。

但是,出於向後兼容性(歷史)的原因,當FileSystemApplicationContextResourceLoader時,情況會發生變化。FileSystemApplicationContext強制所有附加的FileSystemResource實例將所有位置路徑視為相對路徑,不管它們是否以正斜槓開頭。在實踐中,這意味著以下示例是等效的:

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/context.xml");
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("/conf/context.xml");

下面例子也是等效的(即使讓它們有所不同是有意義的,因為一種情況是相對的,另一種情況是絕對的):

FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");

在實踐中,如果你需要文件系統絕對路徑,你需要避免將絕對路與FileSystemResourceFileSystemXmlApplicationContext一起使用,並且強制通過使用file:前綴的UrlResource。下面例子顯示怎樣使用:

// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("file:///conf/context.xml");

作者

個人從事金融行業,就職過易極付、思建科技、某網約車平臺等重慶一流技術團隊,目前就職於某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大數據、數據存儲、自動化集成和部署、分佈式微服務、響應式編程、人工智能等領域。同時也熱衷於技術分享創立公眾號和博客站點對知識體系進行分享。關注公眾號:青年IT男 獲取最新技術文章推送!

博客地址: http://youngitman.tech

CSDN: https://blog.csdn.net/liyong1028826685

微信公眾號:

技術交流群:

Leave a Reply

Your email address will not be published. Required fields are marked *