不幸的是,Java的标准java.net.URL类和用于各种URL前缀的标准处理程序并不足以对所有低级资源进行访问。例如,没有标准化的URL实现可以用于访问需要从类路径获得的资源,或者访问与servlet上下文相关的资源。虽然可以为专门化的URL前缀注册新的处理程序(类似于为前缀(如http:)注册现有的处理程序),但这通常非常复杂,URL接口仍然缺少一些理想的功能,例如检查所指向的资源是否存在的方法。
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。调用者有责任关闭流。exists()
: 返回一个布尔值,指示此资源是否以物理形式实际存在。isOpen()
: 返回一个布尔值,该值指示此资源是否表示具有开放流的句柄。如果为真,则不能多次读取InputStream,并且必须只读取一次,然后关闭,以避免资源泄漏。除了InputStreamResource之外,所有常用资源实现都为false。getDescription()
: 返回此资源的描述,用于处理该资源时的错误输出。这通常是完全限定的文件名或资源的实际URL。其他方法允许您获得表示资源的实际URL或文件对象(如果底层实现兼容并支持该功能)。
资源抽象在Spring本身中得到了广泛的使用,在需要资源时,它是许多方法签名中的参数类型。其他方法在某些Spring APIs(如各种ApplicationContext实现的构造函数),把一个字符串在朴实或简单的形式是用来创建一个上下文的资源适当的实现,或通过特殊前缀字符串路径,允许调用者指定一个特定的资源实现必须创建和使用。
虽然资源接口在Spring中使用得很多,而且每个Spring都使用它,但是在您自己的代码中作为通用实用程序类本身使用它来访问资源实际上非常有用,即使您的代码不知道或不关心Spring的任何其他部分。虽然这将您的代码耦合到Spring中,但它实际上只将它耦合到这个小的实用程序类集合中,这些实用程序类是URL的一个更强大的替代品,并且可以被认为与用于此目的的任何其他库等效。
需要注意的是,资源抽象并没有取代功能:它在可能的地方对功能进行包装。例如,UrlResource包装URL,并使用包装的URL完成其工作。
在Spring中有许多直接提供的资源实现:
UrlResource包装java.net.URL,可以用来访问任何对象,通常是通过一个URL,比如文件、一个HTTP目标,FTP target,等等。所有URL都有标准化的字符串表示,这样适当的标准化的前缀用于指示一个URL从另一个类型。这包括file:用于访问文件系统路径,http:用于通过http协议访问资源,ftp:用于通过ftp访问资源,等等。
UrlResource是由Java代码使用UrlResource构造函数显式创建的,但是在调用API方法时通常是隐式创建的,API方法接受一个字符串参数,该参数表示路径。对于后一种情况,javabean PropertyEditor最终将决定创建哪种类型的资源。如果路径字符串包含一些众所周知的前缀(即classpath:),那么它将为该前缀创建适当的专用资源。但是,如果它不识别前缀,它将假定这只是一个标准的URL字符串,并将创建一个UrlResource。
该类表示应该从类路径中获取的资源。它使用线程上下文类装入器、给定的类装入器或给定的类装入资源。
该资源实现支持java.io.File格式的解析。如果类路径资源驻留在文件系统中,而类路径资源驻留在jar中,并且还没有(通过servlet引擎或任何环境)扩展到文件系统,则不要这样做。为了解决这个问题,各种资源实现总是支持将解析作为java.net.URL。
ClassPathResource是由Java代码使用ClassPathResource构造函数显式创建的,但在调用API方法时通常是隐式创建的,该API方法接受一个字符串参数,该参数表示路径。对于后一种情况,JavaBeans PropertyEditor将识别字符串路径上的特殊前缀classpath:并在这种情况下创建一个ClassPathResource。
这是 java.io.File 的资源实现。文件句柄。它显然支持以文件和URL的形式进行解析。
这是servlet上下文资源的资源实现,解释相关web应用程序根目录中的相对路径。
它始终支持流访问和URL访问,但只允许ava.io.File当web应用程序归档文件展开且资源物理上位于文件系统上时的文件访问。它是否像这样展开并在文件系统上,或者直接从JAR或其他地方(如DB)访问(这是可以想象的),实际上取决于Servlet容器。
给定输入流的资源实现。只有在不适用特定资源实现的情况下,才应该使用它。特别是,如果可能,请选择ByteArrayResource或任何基于文件的资源实现。
与其他资源实现不同,这是已经打开的资源的描述符——因此从isOpen()返回true。如果需要将资源描述符保存在某个地方,或者需要多次读取流,则不要使用它。
这是给定字节数组的资源实现。它为给定的字节数组创建ByteArrayInputStream。
它对于从任意给定的字节数组加载内容非常有用,而无需使用单一用途的InputStreamResource。
ResourceLoader接口是由可以返回(即加载)资源实例的对象实现的。
public interface ResourceLoader {
Resource getResource(String location);
}
所有应用程序上下文实现ResourceLoader接口,因此可以使用所有应用程序上下文获取资源实例。
当您在特定的应用程序上下文中调用getResource(),并且指定的位置路径没有特定的前缀时,您将得到一个适合该特定应用程序上下文的资源类型。例如,假设对ClassPathXmlApplicationContext实例执行了以下代码片段:
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
返回的是ClassPathResource;如果对FileSystemXmlApplicationContext实例执行相同的方法,您将获得FileSystemResource。对于WebApplicationContext,您将返回一个ServletContextResource,等等。
因此,您可以以适合特定应用程序上下文的方式加载资源。
另一方面,您也可以通过指定特殊的classpath: prefix来强制使用ClassPathResource,而不管应用程序上下文类型如何。
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");
下表总结了将字符串转换为资源的策略:
Table 8.1. Resource strings
Prefix | Example | Explanation |
---|---|---|
classpath: |
|
Loaded from the classpath. |
file: |
|
Loaded as a |
http: |
|
Loaded as a |
(none) |
|
Depends on the underlying |
[1] But see also Section 8.7.3, “FileSystemResource caveats”. |
ResourceLoaderAware接口是一个特殊的标记接口,它标识希望提供ResourceLoader引用的对象。
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
当一个类实现ResourceLoaderAware并被部署到应用程序上下文中(作为spring托管的bean)时,它被应用程序上下文中识别为ResourceLoaderAware。然后应用程序上下文将调用setResourceLoader(ResourceLoader),将自己作为参数提供(请记住,Spring中的所有应用程序上下文都实现了ResourceLoader接口)。
当然,由于ApplicationContext是ResourceLoader, bean也可以实现ApplicationContextAware接口,并直接使用提供的应用程序上下文来加载资源,但是一般来说,如果只需要专用的ResourceLoader接口,那么最好使用它。代码只会耦合到资源加载接口,这可以看作是一个实用程序接口,而不是整个Spring ApplicationContext接口。
在spring2.5中,您可以依赖于ResourceLoader的自动连接,作为实现ResourceLoaderAware接口的替代方案。“传统”构造函数和byType自动连接模式(如7.4.5节所述,“自动连接协作者”)现在能够分别为构造函数参数或setter方法参数提供类型ResourceLoader的依赖项。为了获得更大的灵活性(包括自动连接字段和多个参数方法的能力),可以考虑使用新的基于注释的自动连接特性。在这种情况下,ResourceLoader将被自动连接到一个字段、构造函数参数或方法参数中,只要所讨论的字段、构造函数或方法携带@Autowired注释,该参数就期望得到ResourceLoader类型。有关更多信息,请参见7.9.2节“@Autowired”。
如果bean本身要通过某种动态过程确定并提供资源路径,那么bean使用ResourceLoader接口加载资源可能是有意义的。例如,可以考虑加载某种类型的模板,其中所需的特定资源取决于用户的角色。如果资源是静态的,那么完全消除ResourceLoader接口的使用是有意义的,只需让bean公开它需要的资源属性,并期望它们将被注入其中。
所有应用程序上下文都注册并使用一个特殊的JavaBeans PropertyEditor,该编辑器可以将字符串路径转换为资源对象,这使得插入这些属性变得非常简单。因此,如果myBean具有Resource类型的模板属性,可以为该资源配置一个简单的字符串,如下所示:
请注意,资源路径没有前缀,因此由于应用程序上下文本身将用作ResourceLoader,因此资源本身将通过ClassPathResource、FileSystemResource或ServletContextResource (视情况而不同)加载,具体取决于上下文的确切类型。
如果需要强制使用特定的资源类型,则可以使用前缀。下面两个示例展示了如何强制ClassPathResource和UrlResource(后者用于访问文件系统文件)。
应用程序上下文构造函数(对于特定的应用程序上下文类型)通常将字符串或字符串数组作为资源(如组成上下文定义的XML文件)的位置路径。
当这样的位置路径没有前缀时,从该路径构建的用于加载bean定义的特定资源类型取决于特定的应用程序上下文,并且适合于该应用程序上下文。例如,如果您按照以下方式创建ClassPathXmlApplicationContext:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
bean定义将从类路径加载,因为将使用ClassPathResource。但是如果您按照以下方式创建FileSystemXmlApplicationContext:
pplicationContext ctx =
new FileSystemXmlApplicationContext("conf/appContext.xml");
bean定义将从文件系统位置加载,在本例中是相对于当前工作目录。
注意,在位置路径上使用特殊的类路径前缀或标准URL前缀将覆盖为加载定义而创建的默认资源类型。所以这FileSystemXmlApplicationContext……
ApplicationContext ctx =
new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
将实际从类路径加载其bean定义。但是,它仍然是FileSystemXmlApplicationContext。如果随后将其用作ResourceLoader,则任何未加前缀的路径仍将被视为文件系统路径。
构造ClassPathXmlApplicationContext实例——快捷方式
ClassPathXmlApplicationContext公开了许多构造函数,以支持方便的实例化。基本思想是只提供一个字符串数组,其中只包含XML文件本身的文件名(不包含主要路径信息),还提供一个类;ClassPathXmlApplicationContext将从提供的类派生路径信息。
希望通过一个例子可以清楚地说明这一点。考虑这样一个目录布局:
com/
foo/
services.xml
daos.xml
MessengerService.class
一个ClassPathXmlApplicationContext实例,由'services.xml'和'daos.xml'中定义的bean组成,可以这样实例化……
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"services.xml", "daos.xml"}, MessengerService.class);
有关各种构造函数的详细信息,请参阅ClassPathXmlApplicationContext javadoc。
应用程序上下文构造函数值中的资源路径可能是一个简单的路径(如上所示),它具有到目标资源的一对一映射,或者也可能包含特殊的“classpath*:”前缀和/或内部ant样式的正则表达式(使用Spring的PathMatcher实用程序匹配)。后者都是有效的通配符
这种机制的一个用途是在进行组件样式的应用程序组装时。所有组件都可以将上下文定义片段“发布”到一个众所周知的位置路径,当使用classpath*:前缀的相同路径创建最终的应用程序上下文时,所有组件片段将被自动获取。
请注意,这种通配符特定于在应用程序上下文构造函数中使用资源路径(或者在直接使用PathMatcher实用程序类层次结构时),并在构建时解析。它与资源类型本身无关。不能使用classpath*:前缀来构造实际的资源,因为资源一次只指向一个资源。
Ant-style Patterns
当路径位置包含ant样式的模式时,例如:
/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml
解析器遵循更复杂但已定义的过程来尝试解析通配符。它为最后一个非通配符段的路径生成资源,并从中获取URL。如果这个URL不是jar: URL或特定于容器的变体(例如,zip:在WebLogic中,wsjar在WebSphere中,等等),那么就是java.io.File从它获取,并通过遍历文件系统来解析通配符。在jar URL的情况下,解析器要么从中获取java.net.JarURLConnection,要么手动解析jar URL,然后遍历jar文件的内容来解析通配符。
影响可移植性
如果指定的路径已经是一个文件URL(由于基本ResourceLoader是一个文件系统URL,所以可以显式或隐式地使用),那么通配符就可以保证以完全可移植的方式工作。
如果指定的路径是类路径位置,那么解析器必须通过Classloader.getResource()调用获得最后一个非通配符路径段URL。因为这只是路径的一个节点(而不是末尾的文件),所以它实际上是未定义的(在类加载器javadoc中),在本例中URL的确切类型是什么。实际上,它总是java.io。类路径资源解析到文件系统位置的目录,或者某种类型的jar URL,类路径资源解析到jar位置的目录。不过,这种操作仍然存在可移植性问题。
如果为最后一个非通配符段获取jar URL,则解析器必须能够从中获得java.net.JarURLConnection,或者手动解析jar URL,才能遍历jar的内容并解析通配符。这在大多数环境中都可以工作,但在其他环境中会失败,强烈建议在依赖jar之前在特定环境中对资源的通配符解析进行全面测试。
The classpath*: prefix
在构造基于xml的应用程序上下文时,位置字符串可以使用特殊的classpath*: prefix:
ApplicationContext ctx =
new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
这个特殊前缀指定必须获得与给定名称匹配的所有类路径资源(在内部,这实际上是通过ClassLoader.getResources(…)调用实现的),然后合并成最终的应用程序上下文定义。
通配符类路径依赖于底层类加载器的getResources()方法。由于现在大多数应用服务器都提供自己的类加载器实现,所以它们的行为可能有所不同,尤其是在处理jar文件时。
检查classpath*是否工作的一个简单测试是使用类加载器从类路径上的jar中加载文件:getClass(). getclassloader (). getresources ("
classpath*:前缀还可以与位置路径其余部分的PathMatcher模式组合在一起,例如classpath*:META-INF/*-bean.xml。在这种情况下,解决策略相当简单:在最后一个非通配符路径段上使用ClassLoader.getResources()调用,以获得类装入器层次结构中的所有匹配资源,然后在每个资源上使用与上面描述的通配符子路径相同的PathMatcher解决策略。
其他有关通配符的注意事项
请注意classpath*:在与ant样式模式组合时,除非实际目标文件驻留在文件系统中,否则在模式启动之前,只有在至少一个根目录下才能可靠地工作。这意味着类路径*:*这样的模式。xml可能不会从jar文件的根检索文件,而是只从扩展目录的根检索文件。
Spring检索类路径条目的能力源于JDK的ClassLoader.getResources()方法,该方法只返回传入的空字符串(指示要搜索的潜在根)的文件系统位置。Spring评估URLClassLoader运行时配置和“java.class.path”也出现在jar文件中,但这不能保证会导致可移植的行为。
类路径包的扫描需要类路径中存在相应的目录条目。当您使用Ant构建JAR时,请确保您没有激活JAR任务的file -only开关。此外,在某些环境中,类路径目录可能不会基于安全策略公开,例如JDK 1.7.0_45或更高版本上的独立应用程序(这需要在清单中设置“可信库”);看http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources).
没有附加到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中使用绝对路径,并通过使用file: URL前缀强制使用UrlResource。
// actual context type doesn't matter, the Resource will 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");