首页 > 代码库 > Spring官方文档翻译——5.资源
Spring官方文档翻译——5.资源
5. 资源
5.1 简介
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.资源