资源
Java标准的java.net.URL
可以处理各种URL资源,但还是不够用来处理低级资源。例如:没有标准的URL实现用来访问相对类路径或者ServletContext
有关的资源。而且缺少合意的功能,比如判断资源是否存在的方法。
1. Resource
Spring的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(); }
上面定义的接口继承的InputStreamSource
接口如下,
public interface InputStreamSource { InputStream getInputStream() throws IOException;
Resource
接口一些重要的方法:
getInputStream()
:定位和打开资源,从读取到的资源返回一个InputStream
,每次调用会返回最新的InputStream
,调用者有责任关闭这个流。exists()
:返回一个boolean
来说明该资源是否物理存在。isOpen()
:返回一个boolean
来说明该资源是否代表一个开放流的句柄。如果true
,该InputStream
不能被读取多次,并且只能读一次然后关闭来避免资源泄漏。所有通用的资源实现都返回false
,除了InputStreamResource
。getDescription()
:返回该资源的描述,在使用资源时被用于错误输出。这通常是完整的文件名或者资源的实际URL。
2. Resource内置实现
UrlResource
ClassPathResource
FileSystemResource
ServletContextResource
InputStreamResource
ByteArrayResource
2.1 UrlResource
UrlResource
包装了java.net.URL
,它可以被用来访问可以通过URL访问的任何对象,例如:文件、HTTP目标、FTP目标等等。所有的URL使用标准化的String
来表示,像标准化的前缀可以表示URL资源的类型,包括使用file:
来访问文件系统路径,http:
来访问HTTP协议资源,ftp:
来访问FTP资源,等等。
我们可以显式地使用UrlResource
的构造方法来创建UrlResource
,不过通常使用一个代表路径的String
参数来隐式地创建。对于后者,会由PropertyEditor
来决定最终创建的Resource
,如果路径中包含通用的前缀(例如:classpath:
),它会通过这个前缀来创建,如果没有标准的前缀,则会把这个路径作为一个标准的URL来创建UrlResource
。例如,使用该实现读取文件资源和网络资源:
public static void main(String[] args) throws Exception { // 读取文件资源 UrlResource fileResource = new UrlResource("file://E:/code/csdn/spring/src/main/resources/ajn.properties"); System.out.println(fileResource.getFile()); // 读取网络资源 UrlResource webResource = new UrlResource("http://codeartist.cn"); InputStream inputStream = webResource.getInputStream(); byte[] bytes = new byte[1024]; while (inputStream.read(bytes) > 0) { System.out.println(new String(bytes, StandardCharsets.UTF_8)); } inputStream.close();
2.2 ClassPathResource
ClassPathResource
表示从类路径获取的资源,它使用线程上下文加载器、给定的类加载器或给定的类来加载资源。
当类路径上资源存在于文件系统中,ClassPathResource
支持以java.io.File
的形式访问;当类路径上的资源存于尚未解压的Jar包中,ClassPathResource
就不再支持以java.io.File
的形式访问。Spring中的各类Resource
的实现都支持以java.net.URL
的形式访问。
可以显式使用ClassPathResource
构造函数来创建一个ClassPathResource
,不过通常我们可以在调用一个api方法时,使用一个代表路径的String
参数来隐式创建一个ClassPathResource
。对于后一种情况,会由一个PropertyEditor
来识别路径中classpath:
前缀,从而创建一个ClassPathResource
。例如:使用该实现来获取资源:
public static void main(String[] args) throws Exception { ClassPathResource classPathResource = new ClassPathResource("ajn.properties"); System.out.println(classPathResource.getFile()); }
2.3 FileSystemResource
这是针对java.io.File
类和java.nio.file.Path
接口的Resource
实现,它可以支持File
和URL
的解析。例如:
public static void main(String[] args) throws Exception { FileSystemResource fileSystemResource = new FileSystemResource("E:/code/csdn/spring/src/main/resources/ajn.properties"); System.out.println(fileSystemResource.getFile()); }
2.4 ServletContextResource
为了获取相对web应用根目录的ServletContext
资源的Resource
实现。
它支持以流的形式和URL的形式访问,但只有当web应用解压后资源是在文件系统里物理存在的时候才允许以java.io.File
的形式访问。web应用是否已解压和资源是否存在文件系统,或者直接访问JAR或者其他方式(如访问数据库)实际上是取决于Servlet容器。
3. ResourceLoader
实现了ResourceLoader
接口的对象可以返回(或者说加载)Resource
实例。下面是接口定义:
public interface ResourceLoader { Resource getResource(String location); }
所有的ApplicationContext都实现了ResourceLoader
接口,因此,所有的ApplicationContext都可以获得Resource
接口。
当你在一个特殊的ApplicationContext调用getResource()
方法,而指定的位置路径不包含特定的前缀,Spring会根据当前的ApplicationContext来决定返回哪一种Resource
类型。假设下面的代码是通过ClassPathXmlApplicationContext
实例来调用的:
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
如果是ClassPathXmlApplicationContext
实例调用,会返回ClassPathResource
。如果同样的方法是FileSystemXmlApplicationContext
实例调用,会返回FileSystemResource
。对于WebApplicationContext
,它会返回一个ServletContextResource
。它会对于每个上下文返回对应的对象。总的来说,你也可以在特定的ApplicationContext里加载特定的资源。
另一种场景是不管ApplicationContext是什么类型,你需要强制加载ClassPathResource
来使用,这时需要使用特定的前缀classpath:
来,如下所示:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
同样的,你可以通过标准的java.net.URL
前缀来强制获取UrlResource
资源,如下面示例:
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt"); Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt");
下面是总结一下Spring根据各种位置路径加载资源的策略:
前缀 | 示例 | 含义 |
classpath: | classpath:com/myapp/config.xml |
从classpath为根路径开始加载 |
file: | file:///data/config.xml |
以URL 形式从文件系统中加载 |
http: | http://myserver/logo.png |
以URL 形式加载 |
(none) | /data/config.xml |
根据ApplicationContext 的实现确定 |
4. ResourceLoaderAware
ResourceLoaderAware一个特殊的标记接口,用来标记提供ResourceLoader的对象。下面是ResourceLoaderAware
的定义:
public interface ResourceLoaderAware { void setResourceLoader(ResourceLoader resourceLoader); } 当一个类实现了ResourceLoaderAware接口并且部署到
当一个类实现了ResourceLoaderAware
接口并且部署到ApplicationContext中(作为Spring管理的bean),它会被ApplicationContext自动识别,并将自身作为一个参数来调用setResourceLoader(ResourceLoader)
方法(记住,所有的ApplicationContext都实现了ResourceLoader
接口)。创建一个实现该接口的对象并由Spring管理:
public class BeanExample implements ResourceLoaderAware { private ResourceLoader resourceLoader; @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } public ResourceLoader getResourceLoader() { return resourceLoader; } }
public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml"); BeanExample bean = (BeanExample) applicationContext.getBean("beanExample"); ResourceLoader resourceLoader = bean.getResourceLoader(); Resource resource = resourceLoader.getResource("ajn.properties"); System.out.println(resourceLoader == applicationContext); System.out.println(resource); applicationContext.close(); }
为什么上面示例中resourceLoader == applicationContext
会返回true
?
既然一个ApplicationContext
也是一个ResourceLoader
,bean也可以实现
ApplicationContextAware
接口使用ApplicationContext来直接加载资源。然而,一般来说,在都能满足的情况下最好使用专门的ResourceLoader
接口。代码会只耦合到资源加载接口而不是整个Spring的ApplicationContext
接口。
从Spring2.5开始,除了实现ResourceLoaderAware
接口之外,还可以通过自动注入ResourceLoader
,传统的constructor
和byType
注入方式都支持。也支持基于注解的注入方式,可以使用@Autowired
来自动注入。
5. ApplicationContext和资源路径
这一节讲解如何使用资源来创建ApplicationContext,包括使用XML的快捷方式,使用通配符等等。
5.1 构建ApplicationContext
一个ApplicationContext构造方法(具体的ApplicationContext类型)通常会有String或String数组作为资源的位置路径,例如定义上下文的XML文件。
当位置路径没有使用前缀,从指定位置路径创建的Resource
类型(用于加载bean的定义)取决于使用的ApplicationContext。如下面所示,当创建一个ClassPathXmlApplicationContext
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
定义bean的资源会从类路径加载,因为使用了ClassPathResource
。然而,如果需要创建一个FileSystemXmlApplicationContext
:
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");
现在定义bean的资源会从文件系统位置加载(这个例子中,相对当前工作目录)。
如果在位置路径中使用特定的classpath前缀或标准的URL前缀,会覆盖默认加载的Resource
类型,如下:
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
使用FileSystemXmlApplicationContext
加载从classpath定义的资源,然而它仍是一个FileSystemXmlApplicationContext
,如果将它作为ResourceLoader
使用,任何没有加前缀的路径仍会视为文件系统路径。
ClassPathXmlApplicationContext
提供了多个构造方法,以便构建它的实例,例如:
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"}, MessengerService.class);
services.xml
和daos.xml
是在类路径下bean的定义文件。
5.2 构建ApplicationContext资源路径中的通配符
前面说到的构建ApplicationContext的资源路径是简单的路径,每个都会有一对一映射的Resource
目标。另外资源路径还可以是包含特殊的classpath*:
前缀或者内置的Ant风格的正则表达式(使用Spring的PathMatcher
来匹配)。后者都是高效的通配符。
这种机制对组件化应用构成很有用,所有的组件可以将上下文定义片段“发布”到已知的位置路径下,然后,当最后的应用上下文使用同样路径并且带有前缀classpath*:
创建的时候,所有的组件会自动组装。
注意这个通配符特定于ApplicationContext构造器中资源路径的使用(或者当你使用PathMatcher
实用程序类层次结构时)所以它只在构造的时候处理。Resource
类型自己什么也没做,你不能使用classpath*:
来构建实际的Resource
,因为资源只能一次指向一个资源。
Ant风格模式
路径位置可以包含Ant风格模式,如下所示:
/WEB-INF/*-context.xml com/mycompany/**/applicationContext.xml file:C:/some/path/*-context.xml classpath:com/mycompany/**/applicationContext.xml
当路径包含Ant风格模式的时候,解析器会用一段更复杂的程序来试图解析通配符。通过路径产生Resource
取决于最后没有通配符的片段和从中获得的URL。如果这个URL不是jar:
URL或者容器专有的(例如WebLogic中的zip:
,WebSphere中的wsjar
等
等),从中获取java.io.File
并通过遍历文件系统来解析通配符。如果是jar的URL,解析器要么从中获取java.net.JarURLConnection
,要么手动解析jar的URL,然后遍历jar文件内容来解析通配符。
对可移植性的影响
如果指定的路径是URL(要么是隐式,因为基础的ResourceLoader
处理文件系统,要么是显式),通配符完全可以保证可移植性。
如果指定的路径是类路径位置,则解析器必须通过Classloader.getResource()
获取最后一个非通配符路径段。由于这只是路径的一个节点(而不是最后的文件),在这种情况下,它实际上是未定义的(在ClassLoader
的javadocs中)返回的是什么样的URL。
实际上,它始终是一个java.io.File
,它表示类路径资源解析为文件系统位置的目录或某种类型的jar的URL,其中类路径资源解析为一个jar位置。尽管如此,这种操作仍然存在可移植性问题。
如果为最后一个非通配符段获取了一个jar的URL,解析器必须能够从
中获取java.net.JarURLConnection
,或者手动解析jar的URL,以便能够遍历该jar的内容,然后解析通配符。这将在大多数环境中正常工作,但在其他环境中将会失败,并且强烈建议您在依赖它之前,彻底地在您的特定环境中彻底测试来自jar的资源的通配符解析。
classpath*:
前缀
当构建一个基于XML的ApplicationContext时候,位置路径可以使用classpath*:
前缀,如下所示:
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
这特殊前缀表示所有类路径下匹配文件名称的资源都会被获取(本质上调用了ClassLoader.getResources(…)
方法),然后把获取的资源组装成最终的ApplicationContext。
通配符路径的底层依赖类加载器的
getResources()
方法,现在大多数应用服务器提供了自身的类加载器实现,其处理jar文件的形式各有不同,需要在指定服务器上测试classpath:
是否有效。
5.3 FileSystemResource注意事项
FileSystemResource
没有依附FileSystemApplicationContext
,因为FileSystemApplicationContext
并不是一个真正的ResourceLoader
。FileSystemResource
并没有按照预期来处理绝对和相对路径,相对路径是相对当前工作目录,而绝对路径则是相对文件系统的根目录。
为了向后(历史的)兼容,当FileSystemApplicationContext
是ResourceLoade
时做了一些改变,不管路径是否以斜杠开头,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");
实践中,如果你需要使用真实的绝对文件系统路径,你应该避免使用
FileSystemResource
或者FileSystemXmlApplicationContext
,强制通过使用file:
URL前缀的UrlResource
,如下所示:
// 实际上下文类型无关紧要,资源总是UrlResource ctx.getResource("file:///some/resource/path/myTemplate.txt"); // 强制FileSystemXmlApplicationContext通过UrlResource加载 ApplicationContext ctx = new FileSystemXmlApplicationContext("file:///conf/context.xml");