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(); 
     boolean isOpen(); 
     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 haha
View 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:Usershermionereclipse-workspacehelloworld	arget	est-classes	est1.properties
View 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:Usershermionereclipse-workspacehelloworld	arget	est-classes	est1.properties
View 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

原文地址:https://www.cnblogs.com/Hermioner/p/10194500.html