JDK自带的java.nt.URL类可以通过路径来访问各类资源,不同的url前缀指向不同类型的资源,比如文件系统或web资源。不过这个类的功能有所不足,缺少访问classPath或ServletContext下资源的能力,并且缺少一些常用的方法。
因此,Spring定义了一套加载底层资源的机制,用来取代java.net.URL,该机制的核心是两个接口:ResourceLoader和Resource。
Resource接口
Resource接口,替代java.nt.URL,用以指向某个资源,该接口的定义如下:
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;
}
其中几个重要方法的含义如下表:
方法 | 解释 |
---|---|
getInputStream() | 打开资源返回一个InputStream,每次调用都会创建一个新的InputStream,调用者负责关闭 |
exists() | 资源是否存在 |
isOpen() | 资源是否打开,仅InputStreamResource可能返回true,其他必然是false |
getDescription() | 资源的描述,一般是文件全路径名或资源的完整URL |
getURL() | 返回资源URL,前提是该资源位置能够用URL表示 |
getFile() | 返回资源指向的文件,前提是该资源确实指向一个文件 |
Spring框架接口广泛使用Resource,或资源路径作为参数;用户代码也可以直接创建Resouce实例来访问资源,比使用java.net.URL更加方便(如果不介意代码与Spring耦合的话)。
内置Resource实现
Spring提供了几种类型的Resource实现:
- UrlResource
- ClassPathResource
- FileSystemResource
- ServletContextResource
- InputStreamResource
- ByteArrayResource
我们可以通过构造函数直接创建上述类型的Resouce,更多的情况下,Spring的接口接受一个字符串类型的资源路径,内部通过PropertyEditor决定创建那种类型的Resource。
UrlResource
UrlResource包装一个java.net.URL,用于访问可以通过url定位的资源,比如文件,http资源,ftp资源等。所有的URL都能用字符串来表示,通过标准的前缀来区分URL类型,比如file:
访问文件系统路径,http:
用http协议访问web资源,ftp:
用ftp协议访问远程文件。
ClassPathResource
ClassPathResource从类路径加载资源,它使用当前线程的class loader,或给定的classLoader来加载资源。如果资源是本地类路径下的文件,那么getFile方法能返回一个文件对象;资源也有可能在jar包里面,无论何种情况,getURL都能返回一个有效的url。
前面讲过,Spring接口往往接受一个字符串类型的资源路径,前缀为classpath:
的路径指向ClassPathResource。
FileSystemResource
包装java.io.File
和java.nio.file.Path
的资源类型。
ServletContextResource
访问Servlet上下文资源,比如相对于web根目录下的资源。具体的情况,与web容器有关。
InputStreamResource
包装一个已经打开的Inputstream,此类型的资源不能读取多次,也不能保存起来备用。实际上只有InputStreamResource的isOpen方法放回true。由于这些限制,仅仅在其他类型的Resource不适用的时候,才使用该类型。
ByteArrayResource
包装一个byte数组。
ResourceLoader
ResoourceLoader定义了从资源位置加载Resource的接口:
public interface ResourceLoader {
Resource getResource(String location);
}
所有的ApplicationContext都实现了ResoourceLoader,因此我们可以这样使用:
//从类路径加载资源
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
//从网络加载web资源
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
//从哪里加载资源?
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
前两行代码通过路径前缀,明确告知ResoourceLoader加载何种类型的资源,而第三句没有明确指明。Context如何解释这种不带前缀的路径,取决于具体的Context实现;如果是ClassPathXmlApplicationContext,则解释为类路径,如果是FileSystemXmlApplicationContext,则为文件系统路径。
ResourceLoaderAware
用户bean通过实现ResourceLoaderAware接口,可以得到ResourceLoader的引用。
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
当然,现在通过@Autowire也可以直接注入ResourceLoader依赖到属性或构造参数。
实际上,这里得到的就是ApplicationContext本身,因此注入ApplicationContext的引用也是OK的,不过从设计原则出发,依赖更小的接口ResourceLoader更佳。
注入Resource属性
拿到ResourceLoader引用,bean可以动态地加载资源,但是如果bean仅依赖静态资源,那么可以直接声明一个Resource
属性,然后通过属性值注入进去。
比如下面的myBean有一个Resource类型的属性template,bean定义注入了一个字符串值,Spring内部的PropertyEditor会完成类型转换:将字符串解释为资源路径,并通过Context加载Resource。
<bean id="myBean" class="...">
<property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>
ApplicationContext与资源路径
这部分介绍ApplicationContext的具体类型如何使用资源路径,以及路径通配符的细节。
构建ApplicationContext
很多情况下,Context的自身就需要从xml文件来初始化,不同的Context类型对路径有不同的解释,规则在前面已经介绍过:
//从类路径加载
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
//从文件路径加载
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");
//从类路径加载
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
ClassPathXmlApplicationContext还提供了一种便捷的方式,仅需要提供xml文件名,通过一个Class对象来指明包路径:
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"}, MessengerService.class);
通配符
ApplicationContext构造器可以接受多个资源路径,除了一一指定之外,还支持通配符路径。 这里支持两种类型通配符:Ant-style Pattern和 classpath*
。
Ant-style路径请查阅相关文档,classpath*
的意思是从所有匹配的类路径加载资源,而不仅仅是第一个匹配的路径。比如classpath*:conf/appContext.xml
匹配所有conf包下面的appContext.xml;还可以将两者结合起来,classpath:com/mycompany/**/service-context.xml
匹配以com.mycompany打头的包下面的service-context.xml。
不过在使用Ant-style路径来访问jar包里面的资源,可能会有兼容性问题。因为Spring是通过ClassLoader.getResources()来扫描包路径的,这对文件系统下的类路径肯定有效,但不保证对jar包同样有效;尤其当你的代码工作在某个容器中,使用了容器提供的ClassLoader。
FileSystemResource警示
当FileSystemResoure从FileSystemApplicationContext加载的时候,无论传入的路径是否’/'开头,都会被当做相对(当前工作目录)路径。因此new FileSystemXmlApplicationContext("conf/context.xml")
和new FileSystemXmlApplicationContext("/conf/context.xml")
的效果是一样的。
而其他类型的ApplicationContext加载FileSystemResoure则,将以/
开头的路径当做绝对路径,否则当做相对路径。
因此如果实际项目中,想使用绝对文件路径,请使用file:
前缀,避开这种微妙的区别,ctx.getResource("file:///conf/context.xml")
。