这个章节涵盖了Spring怎样处理和在Spring中使用资源文件。包括下面主题:
2.1 介绍
Java的标准java.net.URL
类和标准处理URL前缀变体,不幸地,对于所有访问低级资源的能力还不够。例如,这里没有需要从类路径或相关联的ServletContext
获取资源使用的标准URL实现。当然也可以注册新的处理器为特定URL前缀(类似已经存在的前置处理器,例如:http:
),通常这是十分的复杂,并且URL接口仍然缺乏一些功能描述,例如一种检查所指向资源是否存在的方法。
2.2 Resource接口
Spring的Resource
接口意思是为抽象获取低级别资源提供更多的能力。下面清单显示了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();
}
像Resource
接口定义显示这样,它拓展了InputStreamSource
接口。下面清单显示InputStreamSource
接口的定义:
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
一些Resource
接口中重要的方法:
getInputStream()
:定位和打开资源并且返回从资源中读取的InputStream
。每次调用都返回一个新的InputStream
。我们有责任去关闭流。exists()
:任何一个boolean
指示是否这个资源在物理路径存在。isOpen()
:然后一个boolean
指示此资源是否代表打开流的句柄。如果为true
,InputStream
不能被多次读取且只能读取一次,而且需要关闭资源避免被泄露。对于所有常规资源实现,返回false
,但InputStreamResource
除外。getDescription()
:返回这个资源的描述,以在使用该资源时用于错误输出。这通常是标准文件名或资源的实际URL。
其他方法允许你获取一个真实URL或FIle对象描述资源(如果底层实现是兼容和支持该功能)。
当需要资源
时,Spring自身广泛地使用Resource
抽象,在许多方法签名上参数类型。一些Spring API中的其他方法(例如,各种ApplicationContext
实现的构造函数)采用String形式,该字符串以未经修饰或简单的形式用于创建适合该上下文实现的Resource
,或者通过String路径上的特殊前缀,让调用者指定必须创建并使用特定的资源实现。
虽然Resource
接口在Spring中被大量使用,但是在你自己的代码中作为一个通用的实用程序类来使用它实际上是非常有用的,以便访问资源,即使你的代码不知道或不关心Spring的任何其他部分。虽然这个耦合Spring到你的代码,它仅仅耦合非常小的工具类集合,可以用作URL的更强大替代,并且可以认为与你将用于此目的的任何其他库等效。
Resource
抽象不能替换功能。它尽可能地包装它。例如,UrlResource
包装URL和使用包装的URL去工作。
2.3 内建的Resource实现
Spring包含下面的Resource
实现:
UrlResource
ClassPathResource
FileSystemResource
ServletContextResource
InputStreamResource
ByteArrayResource
2.3.1 UrlResource
UrlResource
包装了java.net.URL
,可用于访问通常可以通过URL访问的任何对象,例如文件,HTTP目标、FTP目标等。所有URL有一个标准化的String表示,因此使用适当的标准化前缀来指示一种URL类型。这包括获取文件系统路径file:
、通过HTTP协议获取资源http:
、通过FTP获取资源ftp:
等等。
UrlResource
是由Java代码通过显式使用UrlResource
构造函数创建的,但通常在调用带有String参数表示路径的API方法时隐式创建。对于后一种情况,JavaBean PropertyEditor
最终决定哪一种Resource
类型被创建。如果字符串包含我们所知的前缀(例如 classpath:
),它创建一个适当的指定前缀的Resource
。然而,如果不能识别前缀,假设字符串是标准的URL字符串并创建一个UrlResource
。
2.3.2 ClassPathResource
这个类代表一个能够从类路径获取的资源。它使用线程上下文类加载器、给定的类加载器或给定的类来加载资源。
这个资源实现支持将解析作为java.io.File
。如果类路径资源驻留在文件系统中,而不是类路径资源驻留在jar中,并且没有(由servlet引擎或其他环境)扩展到文件系统。为了解决这个问题,各种Resource
实现始终支持将解析作为java.net.URL
进行。
当你采用一个String参数描述路径调用API方法时,ClassPathResource
是通过显示使用ClassPathResource
构造函数通过Java代码创建,但是通常隐式地被创建。对于后一种情况,javabean PropertyEditor
识别在字符串路径中指定前缀classpath:
并且在这个场景中创建一个ClassPathResource
。
2.3.3 FileSystemResource
这是java.io.File
和java.nio.file.Path
句柄的Resource
实现。它支持解析作为File和URL。
2.3.4 ServletContextResource
这是ServletContext
资源的Resource
实现,它解释相关Web应用程序根目录中的相对路径。
它始终支持流访问和URL访问,但仅在扩展Web应用程序实现被扩展和资源在实际文件系统上时才允许java.io.File
访问。它是在文件系统上扩展还是直接扩展,或者直接从JAR或其他类似数据库(可以想到的)中访问,实际上取决于Servlet容器。
2.3.5 InputStreamResource
InputStreamResource
是一个给定InputStream
的Resource
实现。仅当没有特定的资源实现时才应使用它。特别是,在可能的情况下,最好选择ByteArrayResource
或任何基于文件的Resource
实现。
对比其他的Resource
实现,这是一个对于已经打开的资源的描述。因此,isOpen()
方法返回true
。如果你需要将资源描述符保留在某个地方或需要多次读取流,请不要使用它。
2.3.6 ByteArrayResource
这是一个给定byte
数组的Resource
实现。它为给定byte
数组创建一个ByteArrayInputStream
。
这对于从任何给定的字节数组加载内容很有用,而不必求助于一次性InputStreamResource
。
参考代码:
com.liyong.ioccontainer.starter.ResourceIocContainer
2.4 ResourceLoader
ResourceLoader
接口是由可以返回(即加载)资源实例的对象实现的。下面清单显示ResourceLoader
定义:
public interface ResourceLoader {
Resource getResource(String location);
}
所有的应用上下文实现ResourceLoader
接口。因此,所有应用上下文可以使用去获取Resource
实例。
当你在指定应用上下文上调用getResource()
,并且定义路径没有指定前缀,你可以获取特定应用上下文中适合的Resource
类型。例如,假定针对ClassPathXmlApplicationContext
实例执行了以下代码片段:
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
针对ClassPathXmlApplicationContext
,这个代码返回一个ClassPathResource
实例。针对FileSystemXmlApplicationContext
如果相同方法被执行,它将返回FileSystemResource
。对于WebApplicationContext
,它将返回一个ServletContextResource
。它将类似地为每一个上下文返回适合的对象。
最后,你可以以适合特定应用程序上下文的方式加载资源。
另一方面,你也可以强制使用ClassPathResource
,不管应用上下文类型,通过指定classpath:
前缀,类似下面例子显示:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
类似地,你可以通过指定标准的java.net.URL
前缀强制使用UrlResource
。下面两个例子使用file
和http
前缀:
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
下面表格总结对于转换String对象到Resource
对象的策略 :
Prefix | Example | Explanation |
---|---|---|
classpath: |
classpath:com/myapp/config.xml |
类路径加载 |
file: |
file:///data/config.xml |
作为URL资源从文件系统加载 FileSystemResource Caveats. |
http: |
https://myserver/logo.png |
作为URL加载 |
(none) | /data/config.xml |
依赖底层ApplicationContext |
2.5 ResourceLoaderAware
接口
ResourceLoaderAware
接口是一个特殊的回调接口,它表示希望使用ResourceLoader
引用提供的组件。下面清单显示ResourceLoaderAware
接口定义:
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
当一个类实现ResourceLoaderAware
并且被部署到应用上下文中(作为Spring管理的bean),它应用上下文作为ResourceLoaderAware
被识别。应用上下文调用setResourceLoader(ResourceLoader)
,应用自身作为参数(记住,Spring中的所有应用程序上下文都实现ResourceLoader
接口)。
因为ApplicationContext
是ResourceLoader
,bean也可以实现ApplicationContextAware
接口并且使用使用应用上下文直接地加载资源。但是,通常,如果需要的话,最好使用专门的ResourceLoader
接口。该代码将仅耦合到资源加载接口(可以视为实用程序接口),而不耦合到整个Spring ApplicationContext
接口。
在应用中的组件,你也可以依赖注入ResourceLoader
作为实现ResourceLoaderAware
接口替代方案。“传统”构造函数和byType
自动装配模式(如“自动装配协作器”中所述)能够分别为构造函数参数或setter方法参数提供ResourceLoader
。为了更大的灵活性(包含自动装配字段和多参数方法),考虑使用基于注解的特性。在这种情况下,ResourceLoader
自动装配到字段、构造函数或方法参数、字段、构造函数或方法携带有@Autowired
注解,就会期望ResourceLoader
类型。更多消息,查看@Autowired
使用。
2.6 依赖Resources
如果Bean本身将通过某种动态过程来确定和提供资源路径,那么对于Bean来说,使用ResourceLoader
接口加载资源可能是有意义的。例如,考虑加载某种模板,其中所需的特定资源取决于用户的角色。如果资源是静态的,那么完全消除ResourceLoader
接口的使用是有意义的,让bean公开它需要的资源属性,并期望将它们注入到bean中。
然后注入这些属性的麻烦之处在于,所有应用程序上下文都注册并使用了特殊的JavaBeans PropertyEditor
,它可以将String路径转换为Resource
对象。如果myBean
有一个Resource
类型模版属性,它可以为资源配置一个简单的字符串,类似下面例子显示:
<bean id="myBean" class="...">
<property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>
注意,资源路径没有前缀。因此,由于应用程序上下文本身将被用作ResourceLoader,所以资源本身是通过ClassPathResource、FileSystemResource或ServletContextResource加载的,这取决于上下文的确切类型。
如果你需要强制指定使用的Resource类型,你可以使用前缀。下面两个例子显示怎样去强制使用ClassPathResource和UrlResource(后面这个例子使用文件系统获取):
<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>
参考代码:
com.liyong.ioccontainer.starter.Resource2IocContainer
2.7 应用上下文和资源路径
这个章节涵盖怎样创建应用上下文与资源,包括与XML一起使用的快捷方式,如何使用通配符以及其他详细信息
2.7.1 构造应用上下文
应用上下文构造通常地采用字符串或资源位置路径字符串,例如构成上下文定义的XML文件。
当位置路径没有前缀时,从该路径构建并用于加载Bean定义的特定Resource类型取决于特定应用程序上下文,并且适用于该特定应用程序上下文。例如,考虑下面例子,创建一个ClassPathXmlApplicationContext
:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
bean定义从类路径被加载,因为ClassPathResource
被使用。然而,考虑下面例子,创建一个FileSystemXmlApplicationContext
:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/appContext.xml");
bean定义从文件系统位置被加载(在这个场景中,相对于当前工作目录)。
注意,使用位置路径上的特殊类路径前缀或标准URL前缀会覆盖为加载定义而创建的默认资源类型。
ApplicationContext ctx =
new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
使用FileSystemXmlApplicationContext
从类路径加载bean定义。然而,它仍然是一个FileSystemXmlApplicationContext
。如果随后将其用作ResourceLoader
,则任何无前缀的路径仍将视为文件系统路径。
构造ClassPathXmlApplicationContext
实例-快捷方式
ClassPathXmlApplicationContext
暴露许多构造方法去实例化。基本思想是,你只提供一个字符串数组,该字符串数组仅包含XML文件本自身的文件名(不包含前导路径信息),并且还提供一个Class。然后,ClassPathXmlApplicationContext
从提供的类中获取路径信息。
考虑下面路径布局:
com/
foo/
services.xml
daos.xml
MessengerService.class
以下示例显示如何实例化由在名为service.xml
和daos.xml
(位于类路径中)的文件中定义的bean组成的ClassPathXmlApplicationContext
实例:
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"services.xml", "daos.xml"}, MessengerService.class);
查看ClassPathXmlApplicationContext
详细javadock的各种构造。
2.7.2 应用程序上下文构造函数资源路径中的通配符
应用程序上下文构造函数值中的资源路径可以是简单路径(如先前所示),每个路径都具有到目标资源的一对一映射,或者可以包含特殊的“classpath*:
”前缀或内部Ant常规样式的正则表达式(通过使用Spring的PathMatcher
进行匹配)。后者都是有效的通配符。
这种机制使用之一是当你需要组件风格封装应用时。所有组件都可以将上下文定义片段“发布”到一个已知的位置路径,并且,当使用以classpath*:
作为前缀的相同路径创建最终的应用程序上下文时,所有组件片段都会被自动获取。
注意,这种通配符是特定于在应用程序上下文构造函数中使用资源路径的(或者当你直接使用PathMatcher
程序类层次结构时),并在构造时解析。它与资源类型本身无关。你不能使用classpath*:
前缀来构造实际的资源,因为一个资源每次只指向一个资源。
Ant风格模式
路径位置可以包含Ant风格模式,类似下面例子显示:
/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml
当路径位置包含一个Ant风格模式时,解析器允许更复杂程序尝试去解析通配符。它为最后一个非通配符段的路径生成一个资源,并从中获得一个URL。如果这个URL不是jar:
URL或包含特定变体(例如,在WebLogic中的zip:
、在WebSphere中的wsjar等等),从中获得一个java.io.File
,并通过遍历文件系统来解析通配符。对于jar URL,解析器可以从中获取java.net.Jar
URLConnection
,也可以手动解析jar URL,然后遍历jar文件的内容以解析通配符。
影响可移植性
如果指定的路径已经是一个文件URL(由于基本ResourceLoader是一个文件系统,所以它是隐式的,或者是显式的),则保证通配符可以完全可移植的方式工作。
如果指定的路径是类路径位置,则解析器必须通过调用Classloader.getResource()
获得最后的非通配符路径段URL。由于这只是路径的一个节点(而不是末尾的文件),因此实际上(在ClassLoader
javadoc中)未定义确切返回的是哪种URL。。在实践中,它总是一个java.io.File
描述目录(类路径资源解析为文件系统的位置)或一个一些种类(类路径资源解析为jar位置)jar URL。尽管如此,此操作仍存在可移植性问题。
如果为最后一个非通配符段获取了jar URL,则解析器必须能够从中获取java.net.Jar
URLConnection
或手动解析jar URL,以便能够遍历jar的内容并解析通配符。这在大多数环境中确实有效,但在其他环境中则无效,因此我们强烈建议在依赖特定环境之前,对来自jars的资源的通配符解析进行彻底测试。
classpath*:
前缀
当构造基于XML应用上下文时,位置字符串可能使用指定的classpath*:
前缀,类似下面例子显示:
ApplicationContext ctx =
new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
这个特殊的前缀指定必须获取与给定名称匹配的所有类路径资源(内部,实际上是通过调用ClassLoader.getResources(…)
发生的),然后合并形成最终的应用程序上下文定义。
通配符类路径依赖类加载器底层的
getResources()
方法。由于当今大多数应用程序服务器都提供自己的类加载器实现,因此行为可能有所不同,尤其是在处理jar文件时。检查classpath *
是否有效的一个简单测试是使用classloader
从classpath
的jar中加载文件:
getClass().getClassLoader().getResources("<someFileInsideTheJar>")
。尝试对具有相同名称但位于两个不同位置的文件进行此测试。如果返回了不合适的结果,请检查应用程序服务器文档中可能会影响类加载器行为的设置。
你可以在其余的位置路径中将classpath*:
前缀与PathMatcher
模式结合使用(例如,classpath*:META-INF/*-beans.xml
),在这个场景中,解析策略是相当地简单:在最后一个非通配符路径段上使用ClassLoader.getResources()
调用,以获取类加载器层次结构中的所有匹配资源,然后在每个资源之外,对通配符子路径使用前面所述的相同PathMatcher
解析策略。
通配符相关的其他注意
注意,当与ant样式模式结合使用时,除非实际目标文件位于文件系统中,否则classpath*:
只能在模式启动之前可靠地与至少一个根目录一起工作。这意味着诸如classpath * : * .xml
之类的模式可能不会从jar文件的根目录检索文件,而只会从扩展目录的根目录检索文件。
Spring检索类路径条目的能力源于JDK的ClassLoader.getResources()
方法,该方法返回文件系统中的空字符串位置(表示可能要搜索的根)。Spring还会评估jar文件中的URLClassLoader
运行时配置和java.class.path
清单,但这不能保证会导致可移植行为。
类路径包扫描要求在类路径中对于目录条目存在。使用Ant构建JAR时,请勿激活JAR任务的文件开关。另外,在某些环境中,基于安全策略,类路径目录可能不会公开。例如,JDK 1.7.0_45及更高版本上的独立应用程序(这需要在清单中设置“受信任的库”)查看https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources
在JDK9的模块路径上,Spring的类路径扫描通常可以正常进行。强烈建议在此处将资源放入专用目录,以避免在搜索jar文件根目录级别时出现上述可移植性问题。
具有classpath:
的Ant样式模式:如果要搜索的根包在多个类路径位置可用,不能保证找到匹配的资源。考虑下面资源为主例子:
com/mycompany/package1/service-context.xml
现在考虑一个ant样式的路径,有人可能会使用它来尝试查找该文件Ant格式路径
classpath:com/mycompany/**/service-context.xml
这些资源可能只在一个位置,但是当使用诸如前面示例的路径尝试对其进行解析时,解析器将处理getResource( "com/mycompany”)
返回的(第一个)URL。如果这个基础包节点在多个类加载器路径中存在,实际的最终资源可能不存在。因此,在这种情况下,你应该首选使用具有相同Ant样式模式的classpath *:
,该模式将搜索包含根包的所有类路径位置。
2.7.3 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
一起使用,并且强制通过使用file:
前缀的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");
作者
个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。关注公众号:青年IT男 获取最新技术文章推送!
博客地址: http://youngitman.tech
CSDN: https://blog.csdn.net/liyong1028826685
微信公众号:
技术交流群: