在 Java 中,将不同来源的资源抽象成 URL
,通过注册不同的 handler
( URLStreamHandler
) 来处理不同来源的资源的读取逻辑。
然而 URL
没有默认定义相对 Classpath 或 ServletContext 等资源的 handler ,虽然可以注册自己的 URLStreamHandler 来解析特定的 URL 前缀(协议)。但是 URL 也没有提供基本的方法、如检查当前资源是否存在,检查资源是否存在等方法。
URL: 我可以加载各种的资源.......XXX
Spring: 你是个好人
Spring 实现了自己的资源加载策略
- 职能划分清楚。资源的定义和资源的加载有明确的界限
- 统一的抽象,统一的资源定义和资源加载策略。
统一资源
org.springframework.core.io.Resource
接口抽象了所有 Spring 内部使用到的底层资源,如 File、URL、Classpath。
public interface Resource extends InputStreamSource { ....... ....... } 复制代码
而 org.springframework.core.io.InputStreamSource
封装任何能返回 InputStream 的类、如 File、Classpath 下的资源和 ByteArray,该接口只有一个方法 getInputStream
public interface InputStreamSource { /** * 表示任意形式的资源都可以被转换成输入流、最好每一次调用这个方法的时候都是返回一个新的InputStream * 看子类{@link FileSystemResource}符合这个要求 */ InputStream getInputStream() throws IOException; } 复制代码
Resource 接口提供了比 URL 更加多和便捷的方法
idea 截图
对于不同来源的资源文件都有相应的 Resource 实现
- 文件 FileSystemResource
- classpath 资源 ClassPathResource
- url 资源 URLResource
- InputStream 资源 InputStreamResource
- byte 数组资源 ByteArrayResource
idea 类图
其中 AbstractResource 实现了 Resource 接口中大多数的方法,如果我们要自定义 Resource ,可以继承这个类,而不是直接实现 Resource
idea 截图
统一资源加载
org.springframework.core.io.ResourceLoader
是 Spring 资源加载的抽象
public interface ResourceLoader { /** * Pseudo URL prefix for loading from the class path: "classpath:". */ String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; /** * 根据 资源路径 返回一个Resource 句柄、但不确保这个Resource 一定存在、需要调用 Resource exists 方法 * 判断 * URL位置资源,如”file:C:/test.dat” * ClassPath位置资源,如”classpath:test.dat” * 相对路径资源,如”WEB-INF/test.dat”,此时返回的Resource实例根据实现不同而不同 * * 要可重用、可多次调用 getInputStream */ Resource getResource(String location); @Nullable ClassLoader getClassLoader(); } 复制代码
idea 类图
主要实现类就是 DefaultResourceLoader
,其中最核心的方法就是 getResource
@Override public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); // 协议解释 for (ProtocolResolver protocolResolver : getProtocolResolvers()) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } } // 如果以/ 开头、则创建 ClassPathContextResource、其实也是一个 ClassPathResource 是他的子类、 类路径的资源 if (location.startsWith("/")) { return getResourceByPath(location); } else if (location.startsWith(CLASSPATH_URL_PREFIX)) { // 如果以 classpath 开头 则 创建ClassPathResource return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // Try to parse the location as a URL... URL url = new URL(location); return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. return getResourceByPath(location); } } } 复制代码
其中 ProtocolResolver
@FunctionalInterface public interface ProtocolResolver { @Nullable Resource resolve(String location, ResourceLoader resourceLoader); } 复制代码
主要用于处理用户自定义资源协议的,我们可以通过实现此接口来解释我们自定义的资源协议,只需要将实现类对象添加到 DefaultResourceLoader
中
public void addProtocolResolver(ProtocolResolver resolver) { Assert.notNull(resolver, "ProtocolResolver must not be null"); this.protocolResolvers.add(resolver); } 复制代码
ResourcePatternResolver
在 ResourceLoader 接口的基础上增加了 Resource[] getResources(String locationPattern)
方法,支持根据指定的路径匹配模式每次返回多个 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 { // Generally only look for a pattern after a prefix here, // and on Tomcat only after the "*/" separator for its "war:" protocol. 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)}; } } } 复制代码
涉及到的设计模式
面试的时候
面试官: 看过什么框架的源码吗
菜鸡的我: 我看过 Spring 的源码
面试官: 那你说说 Spring 中用到了什么设计模式
菜鸡的我: 额....好像...有....我忘记了
责任链模式
在资源定义和资源加载这一块中,我们可以看到在 DefaultResourceLoader 中的 ProtocolResolver,
private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4); public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); // 协议解释 for (ProtocolResolver protocolResolver : getProtocolResolvers()) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } } ..... ..... 复制代码
一个加载资源的请求,会被其中一个 ProtocolResolver 去解释成一个 Resource 或者没有一个 ProtocolResolver 能处理这个请求并返回一个 Resource (最终被后面的默认解析成Resource)。这一个过程其实算是使用到了责任链模式的,只是不是一个纯正的责任链模式。
阎宏博士的《JAVA与模式》一书中是这样描述责任链(Chain of Responsibility)模式的
责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
纯与不纯的责任链模式
- 纯的责任链模式: 要么处理这个请求、要么完全不处理这个请求并将这个请求传递给下一个处理器;不纯的责任链模式:某个处理器处理了一部分这个请求的业务,然后又将剩下的传递给下一个处理器让其继续处理
- 纯的责任链模式: 一个请求必须被一个请求处理器所处理;不纯的责任链模式:这个请求可能没有被任何一个处理器处理
而对于怎么存储下一个 handler 的引用,当然可以在当前 handler 中存有下一个 handler 的引用,但是更加常用的还是使用数组或者列表将所有 handler 存储起来,然后进行链式调用处理请求,如 servlet 的filter即使如此。
策略模式
不同的 ResourceLoader 对应着不同的资源加载策略
idea 类图
在 org.springframework.context.support.GenericApplicationContext
类中
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry { private final DefaultListableBeanFactory beanFactory; @Nullable private ResourceLoader resourceLoader; public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public Resource getResource(String location) { if (this.resourceLoader != null) { return this.resourceLoader.getResource(location); } // 最终调用 DefaultResourceLoader 的 getResource return super.getResource(location); } 复制代码
这个不就是策略模式吗