Spring08-----IoC容器ApplicationContext
作為Spring提供的較之BeanFactory更為先進的IoC容器實現,ApplicationContext除了擁有 BeanFactory支持的所有功能之外,還進一步擴展了基本容器的功能,包括BeanFactoryPostProces- sor、BeanPostProcessor以及其他特殊類型bean的自動識別、容器啟動後bean實例的自動初始化、 國際化的信息支持、容器內事件發布等。真是“青出於藍而勝於藍”啊!
在介紹ApplicationContext之前先引入資源的概念。
一. Resource接口
在日常程序開發中,處理外部資源是很繁瑣的事情,我們可能需要處理URL資源、File資源資源、ClassPath相關 資源、服務器相關資源(JBoss AS 5.x上的VFS資源)等等很多資源。因此處理這些資源需要使用不同的接口,這就 增加了我們系統的復雜性;而且處理這些資源步驟都是類似的(打開資源、讀取資源、關閉資源),因此如果能抽象 出一個統一的接口來對這些底層資源進行統一訪問,是不是很方便,而且使我們系統更加簡潔,都是對不同的底層資 源使用同一個接口進行訪問。
Spring 提供一個Resource接口來統一這些底層資源一致的訪問
1. Resource接口API
Spring的Resource接口代表底層外部資源,提供了對底層外部資源的一致性訪問接口。
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource { boolean exists(); boolean isReadable(); booleanisOpen(); URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; String getFilename(); String getDescription(); }
(1)InputStreamSource接口解析
getInputStream:每次調用都將返回一個新鮮的資源對應的java.io. InputStream字節流,調用者在使用完畢 後必須關閉該資源。
(2)Resource接口繼承InputStreamSource接口,並提供一些便利方法:
exists:返回當前Resource代表的底層資源是否存在,true表示存在。
isReadable:返回當前Resource代表的底層資源是否可讀,true表示可讀。
isOpen:返回當前Resource代表的底層資源是否已經打開,如果返回true,則只能被讀取一次然後關閉以避 免資源泄露;常見的Resource實現一般返回false。
getURL:如果當前Resource代表的底層資源能由java.util.URL代表,則返回該URL,否則拋出IOException。
getURI:如果當前Resource代表的底層資源能由java.util.URI代表,則返回該URI,否則拋出IOException。
getFile:如果當前Resource代表的底層資源能由java.io.File代表,則返回該File,否則拋出IOException。
contentLength:返回當前Resource代表的底層文件資源的長度,一般是值代表的文件資源的長度。
lastModified:返回當前Resource代表的底層資源的最後修改時間。
createRelative:用於創建相對於當前Resource代表的底層資源的資源,比如當前Resource代表文件資源 “d:/test/”則createRelative(“test.txt”)將返回表文件資源“d:/test/test.txt”Resource資源。
getFilename:返回當前Resource代表的底層文件資源的文件路徑,比如File資源“file://d:/test.txt”將返回 “d:/test.txt”,而URL資源http://www.javass.cn將返回“”,因為只返回文件路徑。
getDescription:返回當前Resource代表的底層資源的描述符,通常就是資源的全路徑(實際文件名或實際 URL地址)。
Resource接口提供了足夠的抽象,足夠滿足我們日常使用。而且提供了很多內置Resource實現: ByteArrayResource、InputStreamResource 、FileSystemResource 、UrlResource 、ClassPathResource、 ServletContextResource、VfsResource等。
2. 內置Resource實現
(1)ByteArrayResource
ByteArrayResource代表byte[]數組資源,對於“getInputStream”操作將返回一個 ByteArrayInputStream。
首先讓我們看下使用ByteArrayResource如何處理byte數組資源:
1 package helloworld; 2 import java.io.IOException; 3 import java.io.InputStream; 4 5 import org.junit.Test; 6 import org.springframework.core.io.ByteArrayResource; 7 import org.springframework.core.io.Resource; 8 9 10 public class HelloTest { 11 @Test 12 public void testByteArrayResource() { 13 Resource resource=new ByteArrayResource("Hello World!".getBytes()); 14 if(resource.exists()) { 15 dumpStream(resource); 16 } 17 } 18 19 private void dumpStream(Resource resource) { 20 InputStream is=null; 21 try { 22 //1.獲取文件資源 23 is=resource.getInputStream(); 24 //2.讀取資源 25 byte[] descBytes=new byte[is.available()]; 26 is.read(descBytes); 27 System.out.println(new String(descBytes)); 28 }catch (IOException e) { 29 e.printStackTrace(); 30 }finally { 31 try { 32 //3.關閉資源 33 is.close(); 34 }catch (IOException e) { 35 36 } 37 } 38 } 39 } 40 41 42 Hello World!View Code
說明:ByteArrayResource可多次讀取數組資源,即isOpen ()永遠返回false。
(2)InputStreamResource
InputStreamResource代表java.io.InputStream字節流,對於“getInputStream ”操作將直接返回該字節 流,因此只能讀取一次該字節流,即“isOpen”永遠返回true。
1 @Test 2 public void testInputStreamResource() { 3 ByteArrayInputStream bis=new ByteArrayInputStream("Hello World!".getBytes()); 4 Resource resource=new InputStreamResource(bis); 5 if(resource.exists()) { 6 dumStream(resource); 7 } 8 Assert.assertEquals(true, resource.isOpen()); 9 } 10 11 測試代碼幾乎和ByteArrayResource測試完全一樣,註意“isOpen”此處用於返回true。View Code
(3)FileSystemResource
FileSystemResource代表java.io.File資源,對於“getInputStream ”操作將返回底層文件的字節流, “isOpen”將永遠返回false,從而表示可多次讀取底層文件的字節流。
假設存在test.txt文件,它中間的內容是haha
1 public class HelloTest { 2 @Test 3 public void testByteArrayResource() { 4 File file=new File("C:\\Users\\houuumin\\Desktop\\test.txt"); 5 Resource resource=new FileSystemResource(file); 6 if(resource.exists()) { 7 dumpStream(resource); 8 } 9 Assert.assertEquals(false, resource.isOpen()); 10 } 11 12 private void dumpStream(Resource resource) { 13 InputStream is=null; 14 try { 15 //1.獲取文件資源 16 is=resource.getInputStream(); 17 //2.讀取資源 18 byte[] descBytes=new byte[is.available()]; 19 is.read(descBytes); 20 System.out.println(new String(descBytes)); 21 }catch (IOException e) { 22 e.printStackTrace(); 23 }finally { 24 try { 25 //3.關閉資源 26 is.close(); 27 }catch (IOException e) { 28 29 } 30 } 31 } 32 } 33 34 hahaView Code
(4)ClassPathResource
ClassPathResource代表classpath路徑的資源,將使用ClassLoader進行加載資源。classpath 資源存在於類路 徑中的文件系統中或jar包裏,且“isOpen”永遠返回false,表示可多次讀取資源。
ClassPathResource加載資源替代了Class類和ClassLoader類的“getResource(String name)”和 “getResourceAsStream(String name)”兩個加載類路徑資源方法,提供一致的訪問方式。
ClassPathResource提供了三個構造器:
public ClassPathResource(String path):使用默認的ClassLoader加載“path”類路徑資源; public ClassPathResource(String path, ClassLoader classLoader):使用指定的ClassLoader加載 “path”類路徑資源; public ClassPathResource(String path, Class<?> clazz):使用指定的類加載“path”類路徑資源,將加 載相對於當前類的路徑的資源;
eg:最後一個舉例:
比如當前類路徑是“cn.javass.spring.chapter4.ResourceTest”,而需要加載的資源路徑是“cn/javass/spring/ chapter4/test1.properties”,則將加載的資源在“cn/javass/spring/chapter4/cn/javass/spring/chapter4/ test1.properties”;
而如果需要 加載的資源路徑為“test1.properties”,將加載的資源為“cn/javass/spring/chapter4/ test1.properties”。
1)使用默認的加載器加載資源,將加載當前ClassLoader類路徑上相對於根路徑的資源;
1 public class HelloTest { 2 @Test 3 public void testClasspathResourceByDefaultClassLoader() throws IOException { 4 Resource resource=new ClassPathResource("test1.properties"); 5 if(resource.exists()) { 6 dumpStream(resource); 7 } 8 System.out.println("path:"+resource.getFile().getAbsolutePath()); 9 Assert.assertEquals(false, resource.isOpen()); 10 } 11 12 private void dumpStream(Resource resource) { 13 InputStream is=null; 14 try { 15 //1.獲取文件資源 16 is=resource.getInputStream(); 17 //2.讀取資源 18 byte[] descBytes=new byte[is.available()]; 19 is.read(descBytes); 20 System.out.println(new String(descBytes)); 21 }catch (IOException e) { 22 e.printStackTrace(); 23 }finally { 24 try { 25 //3.關閉資源 26 is.close(); 27 }catch (IOException e) { 28 29 } 30 } 31 } 32 }View Code
resource目錄下有一個文件叫做test1.properties,它中間的內容為hello world。
運行結果如下:
1 hello world 2 path:C:\Users\hermioner\eclipse-workspace\helloworld\target\test-classes\test1.propertiesView Code
2)使用指定的ClassLoader進行加載資源,將加載指定的ClassLoader類路徑上相對於根路徑的資源;
1 public class HelloTest { 2 @Test 3 public void testClasspathResourceByClassLoader() throws IOException { 4 ClassLoader cl=this.getClass().getClassLoader(); 5 Resource resource=new ClassPathResource("test1.properties",cl); 6 if(resource.exists()) { 7 dumpStream(resource); 8 } 9 System.out.println("path:"+resource.getFile().getAbsolutePath()); 10 Assert.assertEquals(false, resource.isOpen()); 11 } 12 13 private void dumpStream(Resource resource) { 14 InputStream is=null; 15 try { 16 //1.獲取文件資源 17 is=resource.getInputStream(); 18 //2.讀取資源 19 byte[] descBytes=new byte[is.available()]; 20 is.read(descBytes); 21 System.out.println(new String(descBytes)); 22 }catch (IOException e) { 23 e.printStackTrace(); 24 }finally { 25 try { 26 //3.關閉資源 27 is.close(); 28 }catch (IOException e) { 29 30 } 31 } 32 } 33 }View Code
1 hello world 2 path:C:\Users\hermioner\eclipse-workspace\helloworld\target\test-classes\test1.propertiesView Code
note: classpath指的是編譯後的路徑地址,可以通過build path來查看:
可以看到build path的目的地是helloworld/target/test-classes中
二.訪問資源(ResourceLoader)
ResourceLoader接口用於返回Resource對象;其實現可以看作是一個生產Resource的工廠類。
1. ResourceLoader的API
1 public interface ResourceLoader { 2 String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; 3 Resource getResource(String location); 4 ClassLoader getClassLoader(); 5 }View Code
getResource接口用於根據提供的location參數返回相應的Resource對象;而getClassLoader則返回加載這些 Resource的ClassLoader
Spring提供了一個適用於所有環境的DefaultResourceLoader實現,可以返回ClassPathResource、 UrlResource;還提供一個用於web環境的ServletContextResourceLoader,它繼承了DefaultResourceLoader的 所有功能,又額外提供了獲取ServletContextResource的支持。
ResourceLoader在進行加載資源時需要使用前綴來指定需要加載:“classpath:path”表示返回 ClasspathResource,“http://path”和“file:path”表示返回UrlResource資源,如果不加前綴則需要根據當前上 下文來決定,DefaultResourceLoader默認實現可以加載classpath資源,如代碼所示
1 @Test 2 public void testResourceLoad() { 3 ResourceLoader loader=new DefaultResourceLoader(); 4 Resource resource=loader.getResource("classpath:com/test"); 5 Assert.assertEquals(ClassPathResource.class, resource.getClass()); 6 7 Resource resource2=loader.getResource("file:com/test"); 8 Assert.assertEquals(UrlResource.class, resource2.getClass()); 9 10 Resource resource3=loader.getResource("com/test"); 11 Assert.assertTrue(resource3 instanceof ClassPathResource); 12 }View Code
2. ApplicationContext
ApplicationContext都實現了ResourceLoader,因此可以使用其來加載資源。主要有以下實現類:
ClassPathXmlApplicationContext:不指定前綴將返回默認的ClassPathResource資源,否則將根據前綴來 加載資源;
FileSystemXmlApplicationContext:不指定前綴將返回FileSystemResource,否則將根據前綴來加載資源;
WebApplicationContext:不指定前綴將返回ServletContextResource,否則將根據前綴來加載資源;
其他:不指定前綴根據當前上下文返回Resource實現,否則將根據前綴來加載資源。
3. ResourceLoaderAware接口
ResourceLoaderAware是一個標記接口,用於通過ApplicationContext上下文註入ResourceLoader。
API如下:
1 public interface ResourceLoaderAware { 2 void setResourceLoader(ResourceLoader resourceLoader); 3 }View Code
舉例說明Application是一個ResourceLoader:
1 public class ResourceBean implements ResourceLoaderAware{ 2 private ResourceLoader resourceLoader; 3 4 public void setResourceLoader(ResourceLoader resourceLoader) { 5 this.resourceLoader=resourceLoader; 6 } 7 8 public ResourceLoader getResourceLoader() { 9 return resourceLoader; 10 } 11 }View Code
1 <bean class="com.test.spring.ResourceBean"/>View Code
1 @Test 2 public void test() { 3 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 4 ResourceBean resourceBean=context.getBean(ResourceBean.class); 5 ResourceLoader loader=resourceBean.getResourceLoader(); 6 Assert.assertTrue(loader instanceof ApplicationContext); 7 }View Code
註意此處“loader instanceof ApplicationContext”,說明了ApplicationContext就是個ResoureLoader.
4. 註入Resource
Spring提供了一個PropertyEditor “ResourceEditor”用於在註入的字符串和Resource之間進行轉換。因此可 以使用註入方式註入Resource。
ResourceEditor完全使用ApplicationContext根據註入的路徑字符串獲取相應的Resource.
1 public class ResourceBean2 { 2 private Resource resource; 3 4 public Resource getResource() { 5 return resource; 6 } 7 8 public void setResource(Resource resource) { 9 this.resource = resource; 10 } 11 12 13 }View Code
1 <bean id="bean1" class="com.test.spring.ResourceBean2"> 2 <property name="resource" value="test1.properties"/> 3 </bean>View Code
1 @Test 2 public void test() { 3 ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); 4 ResourceBean2 resourceBean2=context.getBean("bean1",ResourceBean2.class); 5 Assert.assertTrue(resourceBean2.getResource() instanceof ClassPathResource); 6 }View Code
note: 也可以支持Resource數組
note:還可以使用通配符進行匹配一批路徑
參考文獻
《spring揭秘》
https://jinnianshilongnian.iteye.com/blog/1416320
Spring08-----IoC容器ApplicationContext