Java标准的java.net.URL并不足以处理底层资源,比如说没有从classpath或者相对于它的位置载入资源给ServletContext。
尽管可以给特定的前缀(比如http:)注册处理器(handler),但是这样会很复杂,而且URL接口缺少值得期待的功能,比如检查被指向的资源是否存在。
Spring Resource同样也在Spring Boot中广泛使用。
Resource接口
在org.springframework.core.io包里,是更适合用于抽象化存取底层资源的接口。
定义:
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
boolean isFile();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
ReadableByteChannel readableChannel() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
InputStreamSource接口的定义:
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
Resource接口的部分重要的方法:
- getInputStream():定位并打开资源,返回一个InputStream,调用者应记得close()这个流。
- exists():返回一个boolean,表示资源是否真实存在。
- isOpen():返回一个boolean,表示资源是否表示一个打开的流如果返回true,则InputStream不可以被多次读取,必须被读取一次,读取完之后要close防止资源泄漏。除了InputStreamResource的实现之外返回false(因为没有?2)。
- getDescription():返回对于这个资源的描述,用于显示错误信息,通常是资源的全限定文件名或者资源的实际URL。
有的Resource实现同时实现了WritableResource
接口,支持写入数据。
Spring处处使用Resource抽象作为方法参数,或者是接受一个String参数并转换成适当的Resource实现(简单方式),也或者通过特殊的前缀创建特定类型的Resource实现。
尽管Resource接口在Spring中普遍使用,但是实际上可以将Resource作为一个通用的工具用于自己的代码,而不用在意Spring的其他部分。
尽管会和Spring耦合,但也只是耦合这个小小的工具集,是URL接口更好的替代品,就和使用别的库一样。
内置Resource实现
UrlResource
:包装了一个java.net.URL,可用于存取任何用url访问的资源,如文件、HTTPS、FTP等。
所有的URL都有一个标准化的String表示,有合适的前缀来表示URL类型,如:file:、https:、ftp:等。
除了通过构造器创建之外,接受用String表示路径的方法也会间接创建一个UrlResource
对象。
通常一个路径String里面含有特定的前缀,就会自动创建特定的Resource实现类型。
如果使用了无法识别的路径前缀(prefix,如classpath:),就会自动创建UrlResource
。
ClassPathResource
:表示从classpath获取的资源,使用当前线程的classloader,或给定的classloader,也或者给定的一个类用于加载资源。这个Resource实现支持将文件系统下的但是不在jar文件或者被(比如servlet)展开在文件系统下资源解析成java.io.File类型。可以通过构造器创建,但是在使用String作为路径的方法的参数里,使用classpath:前缀会隐式地创建。
FileSystemResource
:用于处理java.io.File的实现,同样支持java.nio.file.Path。实施Spring标准化的字符串路径转换,但是所有的操作通过java.nio.file.Files实现。要获得纯正的java.nio.path.Path支持请用PathResource。FileSystemResource支持解析成File和URL(同时支持)。
PathResource
:处理java.nio.file.Path的实现,通过Path API完成所有操作和转换。支持解析成File和URL(同时支持)。同时实现了WritableResource接口,支持写入数据。实际上就是基于java.nio.path.Path的FileSystemResource的替代,但是有不同的createRelative行为。
ServletContextResource
:用于ServletContext资源的实现,将路径解释成从web应用根目录开始的相对路径。总是支持以stream存取和URL存取,仅当web应用被展开并且资源物理存在于文件系统里才能以java.io.File形式存取。但是,展开资源与否,还是直接读取jar文件,还是从数据库读取,其实取决于servlet容器。
InputStreamResource
:用于处理InputStream的实现。仅在没有合适的Resource实现可用时才用。优先使用ByteArrayResource或者别的基于文件的Resource实现。为了和别的实现做出区分,这个实现是一个用于已经打开的资源的descriptor,所以调用isOpen()会返回true。除非要读取一个流许多次或者将descriptor用于别的地方,否则不要使用。
ByteArrayResource
:用于处理给定字节数组的实现,为给定的字节数组创建ByteArrayInputStream。
多用于从别的地方读取一个数组而不用创建一个一次性的InputStreamResource。
ResourceLoader接口
被设计成可以返回(载入)Resource实例的接口。定义:
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}
所有的容器类型(ApplicationContext)都实现了ResourceLoader接口,所以可用于获取Resource实例。
当通过容器调用getResource(),路径表示的资源没有特殊前缀时,容器会返回对应于容器类型的Resource实例。例如:
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
假设ctx是一个ClassPathXmlApplicationContext类型实例,那么这个调用将返回ClassPathResource类型的Resource实例。如果ctx是FileSystemXmlApplicationContext类型,就返回FileSystemResource。对于WebApplicationContext类型则是返回ServletContextResource类型的实例。
可以通过添加特定的前缀来实现强制创建想要的类型:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
添加了classpath:前缀就会创建ClassPathResource。
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
添加了file:前缀就用FileSystemResource
。
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
使用https:前缀就创建UrlResource。
String前缀转换成Resource对象的规则总结:
Prefix | 例子 | 解释 |
classpath: | classpath:com/myapp/config.xml | 从classpath载入资源 |
file: | file:///data/config.xml | 以URL的形式从文件系统载入资源 |
https: | https://myserver/logo.png | 作为URL载入 |
(none) | /data/config.xml | 取决于具体的ApplicationContext(容器)实现 |
ResourcePatternResolver接口
是ResourceLoader的拓展,定义了将路径解析成Resource对象的策略。定义:
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
这个接口定义了一个特殊的叫classpath*:的前缀,用于匹配classpath下所有的资源。资源路径不能有占位符。jar文件或者classpath下的不同目录可能会有相同名称相同路径的文件。
PathMatchingResourcePatternResolver是一个单独的实现,在ApplicationContext之外也能使用,也被ResourceArrayPropertyEditor使用,用于获得Resource[]类型的bean的属性。
PathMatchingResourcePatternResolver可以将一个特定的路径解析成一个或多个匹配的Resource对象。
源路径可以是一对一映射到目标Resource,也可能是类似classpath*:之类的前缀或ant风格正则表达式。
注意:任何标准ApplicationContext之内的ResourceLoader实际上都是实现了ResourcePatternResolver接口的PathMatchingResourcePatternResolver实例。容器自己实际上也实现了ResourcePatternResolver接口,然后委托给默认的PathMatchingResourcePatternResolver。
ResourceLoaderAware接口
特殊的回调接口,用于识别能提供ResourceLoader引用的组件。定义:
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
当一个类实现了ResourceLoaderAware接口并作为bean注入到容器,它会被容器识别成ResourceLoaderAware。然后容器调用setResourceLoader(ResourceLoader)方法,将容器自己作为参数传进去,因为容器(ApplicationContext)也实现了ResourceLoader接口。
由于容器自己也是一个ResourceLoader,所以bean实现ApplicationContextAware接口也能达成同样目的。但是如果只是想载入资源,使用ResourceLoader接口会更好。这样代码只会和资源加载接口耦合,而不是整个ApplicationContext接口。
在应用组件里,可以通过@Autowired自动装配一个ResourceLoader类型的对象作为ResourceLoaderAware接口的替代。为了更有弹性(自动装配字段和多个方法参数),考虑使用基于注解的自动装配功能:
@Autowired
ResourceLoader resourceLoader;
注意:如果从一个路径下含有一个或多个Resource对象,考虑自动装配个ResourcePatternResolver
实例而不是ResourceLoader。
资源作为依赖
如果bean要通过一系列动态处理来判断或者提供资源路径,那么bean使用ResourceLoader或者ResourcePatternResolver接口来装载资源就很合理。例如一个模版,对特定的用户载入特定的资源。
由于所有的容器类型都注册并使用PropertyEditor类型bean,可以将String路径转换成Resource对象。
例如,有一个MyBean类:
public class MyBean {
private Resource template;
public setTemplate(Resource template) {
this.template = template;
}
}
在xml配置中,template属性可以配置成:
<bean id="myBean" class="example.MyBean">
<property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>
由于没有特殊前缀,所以具体的类型取决于容器类型。如果需要强制转换,可以添加前缀:
<property name="template" value="classpath:some/resource/path/myTemplate.txt">
类型是ClassPathResource,或者:
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>
类型是FileSystemResource。
甚至可以使用@Value注解注入String路径:
@Component
public class MyBean {
private final Resource template;
public MyBean(@Value("${template.path}") Resource template) {
this.template = template;
}
}
注入的String值由PropertyEditor转换,存在于Spring Environment。
如果想要装在路径下的多个资源,可以将注入点定义成数组类型:
@Component
public class MyBean {
private final Resource[] templates;
public MyBean(@Value("${templates.path}") Resource[] templates) {
this.templates = templates;
}
}
例如templates.path的值为classpath*:/config/templates/*.txt,可以载入目录下所有的txt文件资源。