首先,Java SE提供了URL类(统一资源定位器),spring为什么还会提供值么一个功能?
URL说是统一资源定位,但基本实现却只限于网络形式发布的资源的查找和定位工作,基本上 只提供了基于HTTP、FTP、File等协议(sun.net.www.protocol包下所支持的协议)的资源定位功能。
总结:url自身定位太狭隘了。资源这个词范围比较广义:资源能以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在;资源也能存在任何场所,如存在于文件系统、存在于Java应用的Classpath中和url能定位的地方。
Spring中的Resource
Spring提出了一套基于org.springframework.core.io.Resource和 org.springframework.core.io.ResourceLoader接口的资源抽象和加载策略。
之前在构造BeanFactory的时候已经接触过它,如下代码:
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));
其中ClassPathResource就是Resource的一个特定类型的实现,代表的是存在于Classpath中的资源。
在org.springframework.core.io包下还有如下这些实现类
- ByteArrayResource。将字节(byte)数组提供的数据作为一种资源进行封装,如果通过 InputStream形式访问该类型的资源,该实现会根据字节数组的数据,构造相应的ByteArray- InputStream并返回
- ClassPathResource。该实现从Java应用程序的ClassPath中加载具体资源并进行封装,可以使 用指定的类加载器(ClassLoader)或者给定的类进行资源加载。
- FileSystemResource。对java.io.File类型的封装,所以,我们可以以文件或者URL的形 式对该类型资源进行访问,只要能跟File打的交道,基本上跟FileSystemResource也可以。
- UrlResource。通过java.net.URL进行的具体资源查找定位的实现类,内部委派URL进行具 体的资源操作。
- InputStreamResource。将给定的InputStream视为一种资源的Resource实现类,较为少用。 可能的情况下,以ByteArrayResource以及其他形式资源实现代之。
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;
}
ResourceLoader,“更广义的URL”
org.springframework.core.io.ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出。
把ResourceLoader称作统一资源定位器或许 才更恰当一些吧!ResourceLoader定义如下:
/**
* Spring 将资源的定义和资源的加载区分开了
* Resource 定义了统一的资源
* 那资源的加载则由 ResourceLoader 来统一定义
* ResourceLoader,定义资源加载器,主要应用于根据给定的资源文件地址,返回对应的 Resource
*/
public interface ResourceLoader {
/** CLASSPATH URL 前缀。默认为:"classpath:" */
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
/**
* 根据所提供资源的路径 location 返回 Resource 实例
* 但是它不确保该 Resource 一定存在,需要调用 Resource#exist() 方法来判断
*
* 该方法支持以下模式的资源加载:
* URL位置资源,如 "file:C:/test.dat" 。
* ClassPath位置资源,如 "classpath:test.dat 。
* 相对路径资源,如 "WEB-INF/test.dat" ,此时返回的Resource 实例,根据实现不同而不同
*/
Resource getResource(String location);
/**
* Expose the ClassLoader used by this ResourceLoader.
*/
@Nullable
ClassLoader getClassLoader();
}
几种有特点的ResourceLoader
DefaultResourceLoader和FileSystemResourceLoader
DefaultResourceLoader的getResource方法如下
/**
*ResourceLoader 中最核心的方法为 #getResource(String location)
* 它根据提供的 location 返回相应的 Resource 。
* 而 DefaultResourceLoader 对该方法提供了核心实现
* (因为,它的两个子类都没有提供覆盖该方法,
* 所以可以断定 ResourceLoader 的资源加载策略就封装在 DefaultResourceLoader 中)
*/
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
// 首先,通过 ProtocolResolver 来加载资源
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
// 其次,以 / 开头,返回 ClassPathContextResource 类型的资源
if (location.startsWith("/")) {
return getResourceByPath(location);
}// 再次,以 classpath: 开头,返回 ClassPathResource 类型的资源
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}// 然后,根据是否为文件 URL ,是则返回 FileUrlResource 类型的资源,否则返回 UrlResource 类型的资源
else {
try {
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// 最后,返回 ClassPathContextResource 类型的资源
return getResourceByPath(location);
}
}
}
注意:FileSystemResourceLoader是继承 DefaultResourceLoader,且重写了getResourceByPath(String)方法。因此getResourceByPath(String)方法不再是从classpath加载资源并以ClassPathContextResource 类型返回,而是
使getResourceByPath(String)方法从文件系统加载资源并以 FileSystemResource 类型返回
ProtocolResolver
org.springframework.core.io.ProtocolResolver 用户自定义协议资源解决策略,它允许用户自定义资源加载协议。有了 ProtocolResolver 后,我们不需要直接继承 DefaultResourceLoader,改为实现 ProtocolResolver 接口也可以实现自定义的 ResourceLoader
调用 DefaultResourceLoader的addProtocolResolver(ProtocolResolver) 方法加入自定义的ProtocolResolver
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.add(resolver);
}
ProtocolResolver 接口,仅有一个方法 Resource resolve(String location, ResourceLoader resourceLoader)
ResourcePatternResolver和PathMatchingResourcePatternResolver
ResourceLoader 的 Resource getResource(String location) 方法,每次只能根据 location 返回一个 Resource 。org.springframework.core.io.support.ResourcePatternResolver 是 ResourceLoader 的扩展,它支持根据指定的资源路径匹配模式每次返回多个 Resource 实例。
/**
* ResourceLoader 的 Resource getResource(String location) 方法,
* 每次只能根据 location 返回一个 Resource 。
* 当需要加载多个资源时,我们除了多次调用 #getResource(String location) 方法外,别无他法。
* ResourcePatternResolver 是 ResourceLoader 的扩展,
* 它支持根据指定的资源路径匹配模式每次返回多个 Resource 实例
*/
public interface ResourcePatternResolver extends ResourceLoader {
/**
* 新增了一种新的协议前缀 "classpath*:",该协议前缀由其子类负责实现
*/
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
/**
* 支持根据路径匹配模式返回多个 Resource 实例
*/
Resource[] getResources(String locationPattern) throws IOException;
}
PathMatchingResourcePatternResolver类是ResourcePatternResolver接口的实现类,在处理的时候:
非 “classpath*:” 开头,且路径不包含通配符,直接委托给相应的 ResourceLoader 来实现。
其他情况,调用 #findAllClassPathResources(…)、或 #findPathMatchingResources(…) 方法,返回多个 Resource 。
/**
* 非 "classpath*:" 开头,且路径不包含通配符,直接委托给相应的 ResourceLoader 来实现。
* 其他情况,调用 #findAllClassPathResources(...)、或 #findPathMatchingResources(...) 方法
* 返回多个 Resource
*/
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
// 以 "classpath*:" 开头
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
// 路径包含通配符
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {// 路径不包含通配符
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {// 不以 "classpath*:" 开头
// 通常只在这里的前缀后面查找模式
// 而在 Tomcat 上只有在 “*/ ”分隔符之后才为其 “war:” 协议
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
// 路径包含通配符
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
/**
* 当 locationPattern 以 "classpath*:" 开头但是不包含通配符
* 则调用 #findAllClassPathResources(...) 方法加载资源。
* 该方法返回 classes 路径下和所有 jar 包中的所有相匹配的资源
*/
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (path.startsWith("/")) {
path = path.substring(1);
}
// 真正执行加载所有 classpath 资源
Set<Resource> result = doFindAllClassPathResources(path);
if (logger.isTraceEnabled()) {
logger.trace("Resolved classpath location [" + location + "] to resources " + result);
}
// 转换成 Resource 数组返回
return result.toArray(new Resource[0]);
}
/**
* 真正执行加载所有 classpath 资源
*/
protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
Set<Resource> result = new LinkedHashSet<>(16);
ClassLoader cl = getClassLoader();
// 根据 ClassLoader 加载路径下的所有资源
Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
while (resourceUrls.hasMoreElements()) {
// 将 URL 转换成 UrlResource
URL url = resourceUrls.nextElement();
result.add(convertClassLoaderURL(url));
}
if ("".equals(path)) {
// The above result is likely to be incomplete, i.e. only containing file system references.
// We need to have pointers to each of the jar files on the classpath as well...
// 加载路径下得所有 jar 包
addAllClassLoaderJarRoots(cl, result);
}
return result;
}
小结
- Spring 将资源定义成 Resource,由 ResourceLoader 来加载。举个例子就是 xml 文件的位置 —> ResourceLoader —> Resource的流程。
- Resource 接口的默认实现是 AbstractResource ,是一个抽象骨架类,它对 Resource 接口做了一个统一的实现。
- ResourceLoader 接口的默认实现是 DefaultResourceLoader ,在自定义 ResourceLoader 的时候我们除了可以继承该类外还可以实现 ProtocolResolver 接口来实现自定资源加载协议。
- DefaultResourceLoader 每次只能返回单一的资源,所以 Spring 针对这个提供了另外一个接口 ResourcePatternResolver ,该接口提供了返回多个资源的策略。
是不是已经明白了IoC的第一步定位资源的过程。