2. Resources
本章介绍了Spring如何处理资源以及如何在Spring中使用资源。它包括以下主题:
导演
resource接口
值resource实现
ResourceLoader接口
ResourcePatternResolver接口
ResourceLoaderAware接口
作为依赖项的资源
应用情景和资源路径
2.1 介绍
遗憾的事,java的标准java.net.URL类和各种URL前缀的标准处理程序还不满足对底层资源的所有访问。例如,没有标准化的URL实现可用于访问需要从classpath或相对于ServletContext获取的资源。虽然可以为专门的URL前缀注册新的处理程序(类似于为htp等前缀注册的现有处理程序),但这通常相当复杂,而是URL接口仍然缺乏一些理想的功能,例如检查所指向资源是否存在的方法。
2.1 Resource接口
Spring的Resource接口位于org.springframework.core.io包中,旨在成为抽象访问底层资源的功能更强的接口。下面的列表表述了Resource接口。有关详细信息,请参阅Resource javadoc。
正如Resource接口定义的所示,它扩展了InputStreamSource接口。下面的列表显示了InputStreamSource接口的定义:
Resource接口中最重要的方法有
getInputStream():定位并打开资源,返回用于读取资源的InputStream。预计每次调用都会返回一个新的InputStream。关闭数据流是调用者的责任。
exists():返回一个boolean,标示改资源是否实际存在。
isOpen() :返回一个boolean,表示该资源是否代表一个具有开放流的句柄。如果是true,则InputStream不能多次读取,必须只读取一次,然后关闭,以避免资源泄露。对于所有通常的资源实现,都会返回false,单InputStreamResource除外。
getDescription():返回该资源的描述,用于处理资源的错误输出。这通常是资源的全限定文件名或实际URL。
其他方法可让你获得代表资源的实际URL或File对象(如果底层实现兼容并支持该功能)。 对于支持写入的资源,Resource接口的某些实现也会扩展的WritableResource接口。 Spring本身广泛使用Resource抽象,在许多方法签名中作为需要资源师的参数类型。某些Spring API中e其他方法(例如各种ApplicationContext实现的构造函数)会使用string、改参数以未经修饰或简单的形式用于创建合适该上下文实现的Resource,或者通过string路径的特殊前缀,让调用者指定必须创建和使用特定的Resource实现。 虽然Resource接口在Spring和Spring中被大量使用,但实际上,即使你的代码不了解或不关心Spring的任何其他部分,你也可以非常方便的在自己的代码中将其作用访问资源的通用实用程序类。虽然这将你的代码与Spring结合在一起,但实际上只是将其与这一小部分使用程序类结合在一起,而这些使用程序类则可作为URL的更加替代,并可被视为等同于你为此目的而使用的任何其他库。
2.3内置Resource实现
Spring包含了多个内置Resource实现:
URLResource
ClassPathResource
FileSystemResource
PathResource
ServletContextResource
InputStreamResource
ByteArrayResource
有关Spring中可用的Resource实现的完整列表,请参阅Resource javadoc中对的“所有已知实现类”部分。
2.3.1 UrlResource
UrlResource封装了java.net.URL并可用于访问通常可通过URL访问的任何对象,如文件、HTTPS目标、FTP目标等。所有URL都有标准化的string表示法,因此可以使用适当的标准化前缀来区分不同的URL类型。这包括访问文件系统路径的file:通过HTTPS协议访问次元的https:通过FTP访问资源的ftp等。 UrlResource是由java代码通过显式使用UrlResource构造函数创建的,但通常是在调用API方法时隐式创建的,该方法会接受一个用于表示路径的String参数。对于后一种情况,JavaBeans PropertyEditer将最终确定创建那种类型的resource。如果路径字符串包含了一个众所周知的前缀,它将为该前缀创建一个适当的专用resource。但是,如果无法识别前缀,它就会假定该字符串是标准的URL字符串,并创建一个UrlResource。
2.3.2 ClassPathResource
该类表示应从类路径获取的资源。它使用线程上下文类加载器,给定的类加载器或给定的类加载资源。 如果类路径资源驻留在文件系统中,改Resource实现支持以java.io.file的形式进行解析,但对于驻留在jar中且尚未(通过servlet引擎或任何环境)扩展到文件系统的类路径资源,该Resource实现不支持以java.io.File的形式进行解析。为了解决这个问题,各种Resource实现始终支持以java.net.URL. java代码通过显式使用ClassPathResource构造函数来创建ClassPathResource,但当你调用一个接受string参数以表示路径的API方法时,通常会隐式创建string。对于后一种情况,JavaBeans PropertyEditor会识别字符串路径上的特殊前缀classpath:并在这种情况下创建ClassPathResource。
2.3.3 FileSystemResource
这是针对java.io.File句柄的Resource实现。它还支持java.nio.file.Path句柄,应用Spring基于字符串的标准路径转换,但通过java.nio.file.Files API执行所有操作。要获得的基于java.nio.path.Path的纯粹支持,请使用PathResource代替。FileSystemResource支持以File和Url的形式进行解析。
2.3.4 PathResource
这是针对java.nio.file.Path句柄的resource实现,通过Path API执行所有操作和转换。它支持作为File和URL的解析,还实现了扩展的WritableResource接口。PathResource实际上是java.nio.path.Path的纯粹替代品,与FileSystemResource具有不同的createRelative行为。
2.3.5 ServletContextResource
这是针对ServletContext资源的Resource实现,可解释相关网络应用程序根目录中的相对路径。 它始终支持流访问和URL访问,但只有在网络应用程序存档已展开且资源实际位于文件系统上时,才允许java.io.File访问。至于是否已开展并在文件系统上,或者是否直接从JAR或其他地方(如数据库)访问(这是可以想象的),实际上取决于Servlet容器。
2.3.6 InputStreamResource
InputStreamResource是给定InputStream的Resource实现。只有在没有适用的特定Resource实现时,才应使用它。特别的,在可能的情况下,应首选ByteArrayResource或任何基于文件的Resource实现。 与其他的Resource实现不同,这是一个已打开资源的描述符。因此,它会从isOpen()返回true。如果需要将资源描述符保存在某个地方,或者需要多次读取流,请不要使用它。
2.3.7 ByteArraryResource
这是一个针对给定字节数组的Resource实现。它为给定的字节数组创建一个ByteArrayInputStream 它可用于从任意给定的字节数组中加载内容,而无需使用一次性InputStreamResource.
2.4 ResourceLoader接口
ResourceLoader接口应有可以返回(及加载)Resource实例的对象来实现。下面的列表显示了ResourceLoader接口定义:
所有应用上下文都实现了ResourceLoader接口,因此,所有应用上下文都可用于获取Resource实例。 在特定应用上下文中调用getResource() 时,如果指定的位置路径没有特定的前缀,则会返回合适该特定应用上下文的Resource类型。例如,假设下面的代码片段是针对ClassPathXmlApplicationContext实例运行的:
如果针对ClassPathXmlApplicationContext实例,代码将返回ClasPathResource。如果针对FileSystemXmlApplicationContext实例运行相同的方法,它将返回FileSystemResource。对于WebAPplicationContext,它将返回一个ServletContextResource。同样,他也会为每个上下文返回适当的对象。 因此你可以根据塔顶的应用上下文以适当的方式加载资源。 另一方面,你也可以通过指定特殊的classpath:前缀,强制使用ClassPathResource而不管应用程序上下文类型如何,如下例所示:
同样,你可以通过制定任何一个标准java.net.URL前缀,强制使用URLResource。下面的实例使用了file和https前缀:
下表总结了将string对象转换为Resource对象的策略:
Prefix | Example | Explanation |
---|---|---|
classpath | classpath:com/myapp/config.xml | 从classpath加载 |
file | file://data/config.xml | 以URL的形式从文件系统加载。另请参阅FileSystemResource注意事项 |
https | https://myserver/logo.png | 作为URL文件加载 |
无 | /data/config.xml | 取决于底层的ApplicationContext |
2.5 ResourcePatternResolver接口
ResourcePatternResolver接口是ResourceLoader接口的扩展,它定义了将位置模式(例如Ant风格的路径模式)解析为Resource对象的策略。
如上所示,该接口还为类路径中的所有匹配资源定义了一个特殊的classpath* :资源前缀。请注意,在这种情况下,资源位置应为不含占位符的路径,例如classpath* :/config/beans.xml.JAR文件或类路径中的不同目录可以包含多个具有相同路径和相同名称的文件。有关使用classpath* :资源前缀支持通配符的跟多详情,请参阅应用程序上下文构造函数资源路径中的通配符及其子集。 可以检查传入的ResourceLoader(例如,通过ResourceLoaderAware语义提供ResourceLoader)是否也实现了改扩展接口。 PathMatchingResourcePatternResolver是一个独立的实现,可在ApplicationContext外部使用,也可由ResourceArrayPropertyEditor用于填充Resource[] Bean属性。 PathMatchingResourcePatternResolver能够将制定的资源位置路径解析为一个或多个配置的Resource对象。源路径可以是与目标Resource一一映射的简单路径,也可以包含特殊的classpath* :前缀和/和Ant风格的内部正则表达式(使用Spring的org.springframework.util.AntPathMatcher实用程序匹配)。后者实际上都是通配符。
任何标准ApplicationContext中的默认ResourceLoader实际上是实现了ResourcePatternResolver接口的PathMatchingResourcePatternResolver的实例。ApplicationContext实例本身也是如此,它也实现了ResourcePatternResolver接口,并委派给默认的PathMatchingResourcePatternResolver。
2.6 ResourceLoaderAware接口
ResourceLoaderAware接口是一个特殊的回调接口,用于识别哪些希望获得ResourceLoader引用的组件。下面的列表显示了ResourceLoaderAware接口的定义:
当类实现ResourceLoaderAware并部署到应用上下文(作为Spring管理的Bean)中时,应用上下文将其识别为ResourceLoaderAware。然后应用上下文调用setResourceLoader( ResourceLoader)并将自身作为参数(请记住,S人皮能中的所有应用上下文都实现了ResourceLoader接口)
由于ApplicationContext是ResourceLoader,因此bean也可以实现ApplicationContextAware接口,并直接使用提供的应用程序上下文来加载资源。不过,一般来说,如果你只需要使用专门的ResourceLoader接口,那么使用该接口会更好。代码将只与资源加载接口(可视为实用接口)耦合,而不是与整个SpringApplicationContext接口耦合。 在应用程序组件中,你也可以依靠ResourceLoader的自动注入来替代实现ResourceLoaderAware接口。传统的constructor和byType自动接入模式(如Autowiring Collaborators中所述)能够分别为构造函数参数或设置方法参数提供ResourceLoader。要获得更大的灵活性(包括自动连接字段和多个参数方法的能力),请考虑使用基于注解的自动连接功能。在这种情况下,只要有关字段、构造函数或方法带有@Autowired注解,ResourceLoader就会自动连接到期望使用ResourceLoader类型的字段、构造函数参数或方法参数中。有关信息,请参阅使用@Autowired。
2.7 作为依赖项的资源
如果bean本身要通过某种动态流程来确定和提供资源路径,那么bean使用ResourceLoader或ResourcePatternResolver接口来加载资源可能是合理的。例如,考虑加载某种模板,其中所需的特定资源取决于用户的角色。如果资源师静态的,那么完全不需要使用ResourceLoader接口(或ResourcePatternResolver接口),让Bean公开它所需要的Resource属性,并期待他们被注入到Bean中,这样做是合理的。 所有应用程序上下文都注册使用了一个特殊的javaBeansPropertyEditor,它可以将string路径转换为Resource对象,这使得注入这些属性变得轻而易举。例如,下面的MyBean类有一个类型为Resource的template属性。
在xml配置文件中,template属性可配置为该资源的一个简单字符串,如下例所示:
请注意,资源路径没有前缀。因此,由于应用上下文本身将被用作ResourceLoader,资源将通过ClassPathResource、FileSystemResource或ServletContextResource加载,具体取决于应用上下文的确切类型。 如果需要强制使用特定的Resource类型,可以使用前缀。下面两个实例展示了如何强制使用ClassPathResource和URLResource(后者访问文件系统中的文件):
如果重构MyBean类以便与注解驱动的配置一起使用,那么myTempalte.txt的路径可以存储在名为tempalte.path的键下,例如,存储在Spring Environment可用的属性文件中(请参阅环境抽象)。然后,可以使用属性占位符通过@Value注解引用模板路径(请参阅使用@Value)。Spring将以字符串形式获取模板路径的值,而特殊的PropertyEditor将吧字符串转换为Resource对象,并注入到MyBean构造函数中。下面的实例演示了如何实现这一点。
如果我们希望支持在类路径中多个位置的统一路径下发现的多个模板(例如,在类路径中的多个jar中),我们可以使用特殊的classpath* :前缀和通配符将templates.path关键字定义为classpath*:/config/templates/* txt.如果我们按如下方式重新定义MyBean类,Spring将把模板路径模式转换为Resource对象数组,并将其注入到MyBean构造函数中。
2.8 应用程序上下文和资源路径
本节介绍如何使用资源创建应用上下文,包括使用XML的快捷方式、如何使用通配符及其他细节。
2.8.1 构建应用情景
应用上下文构造函数(针对特定应用上下文类型)通常使用字符串或字符串数组作为资源的位置路径,如构成上下文定义的XML文件。 当这样的位置路径没有前缀时,从改路径构建并用于加载Bean定义的特定Resource类型取决于特定的应用程序上下文,并与之相适应。例如,请看下面的实例,它创建一个ClassPathXmlApplicationContext:
Bean定义是从类路径加载,因为使用的是ClassPathResource。但是,请看下面的实例,该实例创建一个FileSystemXMLApplicationContext:
现在,Bean定义是从文件系统位置加载的。 请注意,在位置路径上使用特殊的classpath前缀或标准URL前缀会覆盖为加载Bean定义而创建的Resource默认类型。请看下面的实例:
使用FileSystemXMLApplicationContext可以从classpath加载Bean定义。但是,它仍然是FileSystemXmlApplicationContext。如果随后将其作用ResourceLoader,任何未加前缀的路径仍将被视为文件系统路径。
构建ClassPathXmlApplicationContext实例-快捷方式
ClassPathXmlApplicationContext提供了许多构造函数,以方便实例化。其基本思想是,你可以只提供一个字符串数组,其中只包含XML文件本身的文件名(不包含前导路径信息),同时提供一个class。然后,ClassPathXmlApplicationContext从提供的类中导出路径信息。 请看下面的目录布局:
下面的实例展示了如何实例化由名为services.xml和repositories.xml(位于类路径上)的文件中定义的Bean组成的ClassPathXmlApplicationContext实例:
有关各种构造函数的详细信息,请参阅ClassPathXmlApplicationContext javadoc。
2.8.2 应用程序上下文构造函数资源路径中的通配符
应用上下文构造函数值中的资源路径可以是简单的路径(如前所述),其中每个路径斗与目标Resource有一对一的映射关系,或者可以包含特殊的classpath*: 前缀或ant风格的内部模式(通过使用Spring的PathMatcher工具进行匹配)。后者实际上都是通配符。 这种机制的一个用途是在需要进行组件式应用程序组装时使用。所有组件都可以将上下文定义片段发布到一个总所周知的位置路径上,当使用以classpath* :为前缀的相同路径创建最终的应用程序上下文时,所有组件片段都会被自动接收。 请注意,这种通配符特定于应用上下文构造函数中使用资源路径(或直接使用PathMatcher实际程序类层次结构时),并在构造时解决。它与resource类型本身无关。你不能使用classpath*: 前缀来构造实际的resource,因为资源一次只能指向一个资源。
ant-style Patterns
路径为止可以包含Ant风格的模式,如下例所示:
当路径为止包含ant风格模式时,解析器会采用更复杂的程序来尝试解析通配符。它会为直到最后一个非通配符段的路径生成一个resource并从中获取一个URL。如果该URL不是jar:URL或特定于容器的变体(如WebLogic中的zip:、WebSphere中的wsjar等),则会从中获取java.io.File并通过遍历文件系统来解析通配符。如果是jar URL,解析器会从中获取java.net.JarURLConnection或手动解析jar URL,然后遍历jar文件中的内容来解析通配符。
对可移植性的影响
如果指定的路径已经是fileURL(隐含的原因是基于ResourceLoader是文件系统URL,显式的原因是ResourceLoader是文件系统URL),那么通配符将保证以完全可一致性的方式工作。 如果指定的路径是classpath位置,解析器必须通过调用ClassLoader.getResource() 获得最后一个非通配符路径端URL。由于这只是路径的一个节点(而不是末尾的文件),在这种情况下,究竟会返回那种类型的URL实际上是未定义(在ClassLoader javadoc中)。实际上,他是一个java.io.FIle表示目录(classpath资源解析到文件系统位置)或某种jar URL(classpath资源解析到jar位置)。不过,这种操作还是存在可移植性问题。 如果为最后一个非通配符段获取了jarURL,解析器必须能够从中获取java.net.JarURLConnection或手动解析jarURL,以便于能够读取jar的内容并解析通配符。这种方法在大多数环境中都能生效,但在其他环境中却会失败,因此我们强烈建议在依赖这种方法之前,在特定环境中对来自jar的资源的通配符解析进行彻底测试。
The classpath*:Prefix
在构建基于XML的应用上下文时,位置字符串可以使用特殊的classpath*:前缀,如下例所示:
这个特殊的前缀指定必须获取与给定名称匹配的所有classpath资源(在内部,这基本上是通过调用ClassLoader.getResources(...) 来实现的),然后合并形成最终的应用程序上下文定义。
你也可以将classpath*:前缀与位置路径其余部分中的PathMatcher模式(例如classpath*:META-INF/* -beans.xml)结合起来。在这种情况下,解决策略相当简单:在最后一个非通配符路径段上使用ClassLoader.getResources() 调用,以获取类加载器层次结构中所有匹配的仇怨,然后在每个资源上使用前面描述的PathMatcher解析策略来处理通配符子路径。
有关通配符的其他说明
请注意,classpath* :与Ant风格的模式相结合时,除非实际目标文件位于文件系统中,否则只有在模式启动前至少一个根目录的情况下才能可靠的工作。这意味着,想classpath*:* .xml这样的模式可能无法从jar文件的根目录检索文件,而只能从扩展目录的跟目录检索文件。 Spring检索classpath条目的能力源自JDK的ClassLoader.getResources() 方法,该方法只返回空字符串的文件系统位置(表示要搜索的潜在根)。Spring还会评估jar文件中的URLClassLoader运行配置和java.class.path清单,但这并不能保证带来可移植的行为。
扫描类路径包需要类路径中存在相应的目录条目。使用ant构建jar时,不要激活jar任务的files-only开关。此外,在某些环境中,classpath目录可能不会根据安全策略暴露出来,例如JDK1.7.0_45及更高版本上的独立应用程序(需要在清单中设置“Trusted-Library”)。请参见https: //stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。
如果要搜索的根软件包在多个classpath位置都有,则不保证使用classpath:资源的ant-style模式能找到匹配的资源。请看下面的资源位置示例:
现在,考虑一个ant风格的路径,有人可能会用它来查找该文件:
这样的资源可能只存在于classpath中的一个位置,但当使用前例这样的路径尝试解析它时,解析器会根据getResource("com/mycompany") ;返回的(第一个)URL进行解析。如果该基础包节点存在于多个ClassLoader位置中,则所需要资源可能不存在于找到的第一个位置中。因此,在这种情况下,你应有限使用具有相同ant风格模式的classpath* :,你会搜索包含com.mycompany基本包的所有classpath位置:classpath*:com/mycompany/**/service-context.xml.
2.8.3 FileSystemResource注意事项
未连接到FileSystemApplicationContext的FileSystemResource(也就是说,当FileSystemApplicationContext不是实际的ResourceLoader时)会按照你所期望的方式处理绝对路径和相对路径。相对路径是相当于当前工作目录而言,而绝对路径是相当于文件系统根目录而言的。 不过,处于向后兼容(历史)的原因,当FileSystemApplicationContext是ResourceLoader时,情况会发生变化。FileSystemApplicationContext会强制所有附加的FileSystemResource实例将所有位置路径视为相对路径,无论它们是否以斜线开头。实际上,这意味着示例是等价的:
下面的例子也是等价的(尽管它们的区别是合理的,因为一种情况是相对,另一种情况是绝对的):
实际上,如果需要真正的绝对文件系统路径,应避免使用FileSystemResource或FileSystemXmlApplicationContext的绝对路径,而应使用file:URL前缀来强制使用URLResource。下面的实例展示了如何做到这一点: