首页 > 代码库 > Spring官方文档翻译——5.资源

Spring官方文档翻译——5.资源

5. 资源


5.1 简介


使用标准的java.net.URL类和各种URL前缀处理器并不能满足我们对访问底层资源。比如,没有用于访问classpath下或是相对于ServletContext的资源的标准化URL实现类。尽管可以为特定的URL前缀注册新的处理器(类似现有对http:前缀的处理器),但是这通常比较复杂,而且URL接口仍然缺少一些有用的方法,比如可以检查资源是否存在的方法。

5.2 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();
}

public interface InputStreamSource {
	InputStream getInputStream() throws IOException;
}


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

getInputStream():定位并打开资源,读取资源并返回一个InputStream。每次调用都返回一个新的InputStream。调用者需要手动关闭资源。

exists():返回当前代表的底层资源是否存在

isOpen():如果为true,表示InputStream不能被多次读取,只能读取一次然后关闭以免造成资源泄露。对大多数资源实现类都返回false,除了InputStreamResource。

getDescription():返回当前Resource代表的底层资源的描述符,在使用该资源时输出错误时使用。通常是全限定类名或是资源实际的URL。

其余方法可以让你获取一个真实的且能够代表这个资源的URL或File对象(如果底层实现兼容并且支持这个方法)。

Resource的抽象广泛地应用于Spring自身:当需要一个资源时,在很多方法签名中Resource会作为其中一个参数。另外一些Spring API的方法(比如各种ApplicationContext实现类的构造器),使用一个简单的字符串来创建一个适合这个上下文的资源,或是根据字符串中特殊的前缀来指定Resource的类型。

由于Resource接口在Spring中广泛的被使用。也许你并不在乎Spring的其他部分,但是你可以将它(Resource)作为一个通用的工具类应用于你的代码来访问资源。这仅仅只会耦合一小部分,并且这一些部分将能完全取代URL并提供额外的功能,你完全可以将它当成其他任何一个第三方的lib。

很重要的一点是:Resource抽象并没有替换原来的功能,而是在原有的基础上封装了一层。例如,UrlResource封装了一个URL,然后委托URL来工作。


5.3 内置的 Resource实现

Spring已经内置了一些Resource的实现类可供你使用:


UrlResource

UrlResource封装了一个java.net.URL,可以用来访问任何能够用URL访问的对象,比如文件,HTTP目标,FTP目标等等。所有这些URL都有一个标准化的字符串表示,从而可以用前缀就来表明URL的类型。file:用来访问系统路径,http:通过HTTP协议来访问资源,ftp:通过FTP来访问资源等等。

UrlResource对象可以使用构造器来创建,但是通常是使用一个表示路径的字符串来创建。对于后一种情况,PropertyEditor将会最终决定创建哪种资源。如果表示路径的字符串中包含一些出名的前缀比如:classpath:,那么将会创建一个符合这个前缀的资源。然而,如果没有发现前缀,那么它将会视其为一个标准的URL字符串,然后创建一个UrlResource。


ClassPathResource

这个类表示从classpath下获取的资源。它会使用当前线程上下文类加载器,指定的类加载器,或是指定的类来加载资源。

如果classpath资源存在于文件系统(不包括打包在jar的情况),那么ClassPathResource同样支持用java.io.File来解决。另外,多种Resource实现总是支持用java.net.URL来解决(当然啦,这才是底层实现嘛,只不过是方便和麻烦的关系咯)。

ClassPathResource对象可以使用构造器来创建,但是通常是使用一个表示路径的字符串来创建。对于后一种情况,PropertyEditor将会从中发现classpath:前缀,然后创建一个ClassPathResource。


FileSystemResource

这个Resource实现是为了处理java.io.File,很显然,它也支持用File或URL来解决。


ServletContextResource

这是对应ServletContext资源的实现类,能够解析相对于web应用根目录的路径。

这个类支持流的访问以及URL访问,但仅当web应用被解压缩时才能使用java.io.File来访问。无论web应用是否被解压,或是直接访问JAR或是别的地方(比如DB)实际上取决于Servlet容器。


InputStreamResource

对于一个指定的InputStream的Resource实现类。只有当没有明确的Resource实现可以使用时才会使用它。尽可能优先使用ByteArrayResource或是其余基于文件的Resource实现类。


ByteArrayResource

对应一个指定的字符数组的Resource实现类。它为指定的字符数组创建了一个ByteArrayInputStream。

当从一个指定的字符数组载入内容时,这个类很有用。因为这样就无需去使用一次性的InputStreamResource了。


5.4 ResourceLoader

可以通过实现ResourceLoader接口来返回(也就是加载)Resource实例。

public interface ResourceLoader {
	Resource getResource(String location);
}

所有应用上下文都实现了ResourceLoader接口,因此所有的应用上下文都可以用来取得资源实例。

当你在特定的应用上下文中调用getResource()方法时,若此时资源路径没有指定前缀,那么你将会得到一个适合当前应用上下文的资源。比如,假设下面代码片段中的ctx是ClassPathXmlApplicationContext:

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

那么返回的将会是一个ClassPathResource。而如果同样的方法在FileSystemXmlApplicationContext中调用,那么你将得到一个FIleSystemResource。如果在WebApplicationContext中,那么你将得到一个ServletContextResource。

如此依赖,你可以根据应用上下文来加载资源。

而另一方面,你也可以不管应用上下文的类型来强制加载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");

下面的表格总结了String到Resource转化的策略(此处略)


5.5 ResourceLoaderAware接口

ResourceLoaderAware接口是一个特殊的标记接口,标记那些希望得到ResourceLoader引用的对象。

public interface ResourceLoaderAware {
	void setResourceLoader(ResourceLoader resourceLoader);
}

当一个类实现了ResourceLoaderAware接口并且被部署到了应用上下文中(比如一个Spring管理的bean),它将被应用上下文视为ResourceLoaderAware。然后应用上下文会调用它的setResourceLoader(ResourceLoader)方法,并会提供自身作为参数(记住,Spring中所有的应用上下文都实现了ResourceLoader接口)。

当然,由于一个ApplicationContext是一个ResourceLoader,bean同样可以实现ApplicationContextAware接口来加载资源,但是总的来说,如果只需要资源加载这么一个功能的话,最好使用细化了的ResourceLoader接口。因为这样的话,代码只会与资源加载的接口耦合,这个接口可以当做一个工具接口来使用,而不用与整个Spring的ApplicationContext耦合。

在Spring 2.5中,你可以利用自动装配而无需实现ResourceLoaderAware接口。“传统的”constructor和byType自动装配模式都能够提供ResourceLoader的引用。要想更加灵活(包括自动装配字段和多个参数方法),可以使用基于注解的自动装配。在这种情况下,ResourceLoader将会被自动装配到带有@Autowired注解的字段,构造器参数或是方法参数。


5.6 Resources作为依赖(注入Resources)

如果bean自身将要通过某种动态的方式来决定和提供资源路径,那么它应该使用ResourceLoader接口来加载资源。比如需要根据用户的角色来加载某种模板。如果资源是固定的,就没有必要使用ResourceLoader接口,只需要让bean暴露Resource属性,然后注入即可。

所有的应用上下文注册并使用一个特殊的PropertyEditor,它可以将字符串路径转换成Resource对象,这就将注入Resource属性变得简单了。那么比如myBean有一个Resource类型的template属性,它可以通过一个简单的字符串来注入这个属性,比如:

<bean id="myBean" class="...">
<property name="template" value=http://www.mamicode.com/"some/resource/path/myTemplate.txt"/>


注意这个资源路径没有前缀,而且由于这个应用上下文将被当成ResourceLoader,那么将会根据当前的应用上下文类型来决定被载入的资源是ClassPathResource,FileSystemResource或是ServletContextResource。

可以通过指定前缀来强制使用指定的Resource类型。下面两个例子显示了如果强制使用ClassPathResource和UrlResource。

<property name="template" value=http://www.mamicode.com/"classpath:some/resource/path/myTemplate.txt">>


5.7 应用上下文和Resource路径

构造应用上下文

一个应用上下文的构造器(对一个指定的应用上下文)使用一个字符串或者字符串数组来定位资源,比如构造BeanDefinition的XML文件的路径。

当这样的一个路径没有前缀时,那么将会根据当前应用上下文的类型来决定资源的类型。例如,如果你用如下方式创建一个ClassPathXmlApplicationContext:

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

应用上下文将会从classpath下载入bean定义,并被当成一个ClassPathResource。但是如果你以如下的方式创建一个FileSystemXmlApplicationContext:

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


此时bean定义将会从文件系统的某个位置被载入,相对于当前工作目录的路径。

注意,如果在路径字符串上带有特殊的classpath前缀或是标准的URL前缀的话,那么这将覆盖默认的Resource类型。所以下面的FileSystemXmlApplicationContext实际上将会从classpath来载入bean定义。不过它仍旧还是一个FileSystemXmlApplicationContext。如果它后来又被当做一个ResourceLoader来使用,那么任何不带前缀的路径仍然被看做一个文件系统的路径(注:这里我需要测试一下ClassPathXmlApplicationContext等用file前缀是否生效)。

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

创建ClassPathXmlApplicationContext实例——快捷方式

ClassPathXmlApplicationContext暴露了许多构造器用来为实例化提供方便。最基本的是一个字符串数组参数提供XML文件的文件名(不要路径),另一个参数提供一个Class对象。那么ClassPathXmlApplicationContext将会从提供的Class对象来得到路径。

希望下面的例子可以让你看的更清楚。考虑下面的目录结构:

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

一个ClassPathXmlApplicationContext实例可以被如此实例化:

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

其中services.xml、daos.xml中包含了一些的bean定义。


应用上下文构造器中资源路径中的通配符

应用上下文构造方法中的资源路径参数可能是一个简单的路径(就像上面所示),它和目标资源是一一对应的,或者是包含一个特殊的“classpath*:”前缀和/或内部的Ant风格的正则表达式(使用Spring的PathMatcher工具来匹配)。后两者都是有效的通配符。

这个机制的一个用途是当使用多模块风格的应用装配。所有的模块都可以发布自己的上下文定义片段到一个指定的路径。当最终的应用上下文使用带有前缀“classpath:*”的路径被创建时,所有的组件片段将被自动组合起来。

注意这个通配符要针对应用上下文的构造器(或当直接使用PathMatcher工具类时)来使用,并且是在构造时期就被解析了。它与Resource类型没有关系。不能用classpath*:前缀来创建一个实际的Resource,因为在同一时间Resource和底层资源一一对应的。


Ant风格的表达式

当路径包括一个Ant风格的表达式,比如:

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

解析器会根据一个更加复杂但明确的过程来解析通配符。它将从头直到最后一位非通配的部分来得到一个URL。如果这个URL不是一个“jar:”URL或容器相关的变量(比如WebLogic中的“zip:”,WebSphere中的“wsjar”),那么将能从中得到一个java.io.File,并且会用这个File来遍历文件系统。在jar URL的情况下,解析器会从中得到一个java.net.JarURLConnection或是手动地解析jar URL然后遍历jar文件的内容来解析这个通配符。

对移植性的影响

如果指定的路径是一个文件URL(无论显式还是隐式,因为基础的ResourceLoader是一个文件系统的ResourceLoader),所以这种情况下通配符是可以跨平台工作的。

如果指定的路径是一个classpath位置,那么解析器必须通过调用Classloader.getResource()得到路径的非通配部分的URL。由于这仅仅是路径的一部分(不是文件),所以在这种情况下根本没明确定义会返回何种URL。在实践中,总是返回一个代表目录的java.io.File对象。。。

如果从最后一位非通配的部分得到的是一个jar URL,那么解析器必须能够从中得到一个java.net.JarURLConnection或是手动解析这个jar URL来解析出通配符。这在大多数的环境中都适用,但是某些情况下可能不起作用。关于jar的通配符,强烈建议在你将要使用的具体环境中做个彻底地测试。


classpath*:前缀

当构造一个基于XML的应用上下文,一个路径字符串可能会使用特殊的"classpath*:"前缀:

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

这个特殊的前缀表明了classpath下所有匹配文件名称的资源都会被获取(这本质上通过一个ClassLoader.getResources(...)调用),然后将获取到的资源组装成最终的应用上下文定义。


classpath*:的可移植性

"classpath*:"依赖于底层classloader的getResources()方法。由于大多数应用服务器现在都提供它们自己的classloader实现,而它们的行为可能不同,尤其当处理jar文件时。一个简单的测试可以判断classpath*是否有效:使用classloader来载入classpath下的jar中的一个文件——getClass().getClassLoader().getResources("<someFileInsideTheJar>")。然后将两个同名的文件放在不同的位置来进行如上测试。如果返回了不同的结果,那么就需要查看应用服务器的文档来找到可能会影响classloader行为的设置。

在位置路径的其余部分,classpath:*前缀也能够和一个PathMatcher组合,比如"classpath*:META-INF/*-beans.xml"。在这种情况下,解决的策略相当简单:先得到最靠前的付无通配位置路径segment,然后调用ClassLoader.getResources(segment)来得到所有匹配的资源,然后将 PathMacher 的策略应用于每一个获得的资源。


另一些关于通配符的说明

请注意,当“classpath*:”与Ant风格表达式一起使用时,需要在表达式开始前至少有一个根目录,这样才能生效,除非目标文件在文件系统上。这意味着“classpath*:*.xml”这样的表达式将不会从根目录的jar文件检索,而只会从根目录的扩展目录检索。这要追溯到JDK中ClassLoader.getResources()方法的局限性,当传入一个空字符串时,这个方法返回文件系统位置(也就是潜在的搜索根路径)。

如果在多个类路径上存在所搜索的根包,那么Ant风格的表达式和"classpath:"前缀一起使用时,并不保证能找到匹配的底层资源。这是因为一个底层资源:

com/mycompany/package1/service-context.xml

可能只存在于一个地方,但是当一个路径如下所示时:

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

被解析时,解析器只会对(第一个)通过getResource("com/mycompany")返回的URL进行遍历。如果基础包节点"com.mycompany"存在于多个classloader位置,那么实际的资源可能会在后面,也就说不是第一个,那么解析器就不一定能找到资源了。因此,处理上面情况更好的方式是,使用"classpath*:"来和相同的Ant风格表达式一起工作,它将会搜索所有包含基础包节点的类路径。


FileSystemResource注意事项

一个不由FileSystemApplicationContext载入的FileSystemResource(这就是说,FileSystemApplicationContext不是真正的ResourceLoader)对待绝对路径和相对路径和正常情况一样。相对路径是相对于当前工作目录,而绝对路径则是相对于文件系统的根目录。

然而为了向前兼容(历史的)原因,当FileSystemApplicationContext是ResourceLoader时就不一样了。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一起使用,而是直接使用UrlResource,并指定file:前缀

// actual context type doesn't matter, the Resourcewill 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");




Spring官方文档翻译——5.资源