Spring5源码分析(005)——IoC篇之统一资源加载Resource和ResourceLoader
Posted wpbxin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring5源码分析(005)——IoC篇之统一资源加载Resource和ResourceLoader相关的知识,希望对你有一定的参考价值。
注:《Spring5源码分析》汇总可参考:Spring5源码分析(002)——博客汇总
本文主要介绍 Spring 的统一资源 Resource 及其加载策略 ResourceLoader,目录如下:
- 1、为什么会先从 Resource 和 ResourceLoader 入手进行介绍?
- 2、关于统一资源和资源加载策略
- 3、统一资源 Resource
- 4、统一资源加载策略 ResourceLoader
- 5、总结
- 6、测试案例参考
- 7、参考
- 8、声明
1、为什么会先从 Resource 和 ResourceLoader 入手进行介绍?
为什么开篇会是先对统一资源 Resource 和资源加载 ResourceLoader 来进行分析?因为跟创建 IoC 容器密切相关,无论是 BeanFactory 还是 ApplicationContext。就如官方参考文档提到的 https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#beans-factory-instantiation:
After you learn about Spring’s IoC container, you may want to know more about Spring’s Resource abstraction (as described in Resources), which provides a convenient mechanism for reading an InputStream from locations defined in a URI syntax. In particular, Resource paths are used to construct applications contexts, as described in Application Contexts and Resource Paths.
看看大家都比较熟悉的 spring hello world (此处省略 HelloWorldService 和相关 xml,请自行脑补):
// create and configure beans ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); // retrieve configured instance HelloWorldService helloWorldService = context.getBean("helloWorldService", HelloWorldService.class); // use configured instance helloWorldService.sayHelloWorld();
还有其他的很多创建 ApplicationContext 的例子:
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); ApplicationContext context = new ClassPathXmlApplicationContext("file:C:/config/services.xml"); ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/config/services.xml"); ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/config/services-*.xml"); ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:/config/services.xml"); ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:/config/services-*.xml"); ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:/**/services-*.xml"); ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:/**/*.xml");
没错,这里所涉及到的 xml 都被抽象为 Resource,而加载 Resource(这里指所有的 xml)则需要使用 ResourceLoader,实际上就是 ResourceLoader 这货负责找到所有的 xml(loadBeanDefinitions 前需要找到有 bean 元数据定义的 xml Resource)。
注:虽说在注解使用大行其道,甚至是在 springboot 约定大于配置,基本上可以完全去 XML 化的情况下下,大家对 @ComponentScan、@Service、@Repository、@Component、@Autowired、@Qualifier、@Bean、@Configuration 等这些常用注解应该说是耳熟能详,而且也都是信手沾来,传统的基于 xml 的配置也是越来越少使用了,不过即便如此,xml 配置还是有一定的存在意义的,相对于基于注解的配置,其好处在于:只配置 xml 而非侵入、配置可以中心化管理、增删改 bean 定义元数据无需重新编译。具体参考官网对于两者的一些说明:Are annotations better than XML for configuring Spring? https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#beans-annotation-config
接下来我们看看 org.springframework.beans.factory.support.AbstractBeanDefinitionReader 中的一个重要接口,这里暂时不细讲,只需要知道的是:IoC 容器需要 BeanDefinitionReader 来读取解析配置所有的 BeanDefinition,而配置元数据来源(之一)则是前面配置的 xml。
/** * Load bean definitions from the specified resource location. * <p>The location can also be a location pattern, provided that the * ResourceLoader of this bean definition reader is a ResourcePatternResolver. * @param location the resource location, to be loaded with the ResourceLoader * (or ResourcePatternResolver) of this bean definition reader * @param actualResources a Set to be filled with the actual Resource objects * that have been resolved during the loading process. May be {@code null} * to indicate that the caller is not interested in those Resource objects. * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors * @see #getResourceLoader() * @see #loadBeanDefinitions(org.springframework.core.io.Resource) * @see #loadBeanDefinitions(org.springframework.core.io.Resource[]) */ public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available"); } if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); int count = loadBeanDefinitions(resources); if (actualResources != null) { Collections.addAll(actualResources, resources); } if (logger.isTraceEnabled()) { logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]"); } return count; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { // Can only load single resources by absolute URL. Resource resource = resourceLoader.getResource(location); int count = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } if (logger.isTraceEnabled()) { logger.trace("Loaded " + count + " bean definitions from location [" + location + "]"); } return count; } }
这里的重点之一就是中间这一句,这里的 location 可以当作是前面传递进来的参数,也就是那些 xml 路径。很明显这里就是通过 ResourceLoader 来找到所有的 xml Resource,然后作为 BeanDefinitionReader 的输入来进行解析。
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
至此可以大致知道了为何会对 Resource 和 ResourceLoader 先进行分析的原因。
2、关于统一资源和资源加载策略
官网对于 org.springframework.core.io.Resource 的一段说明: https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#resources-introduction
Java’s standard java.net.URL class and standard handlers for various URL prefixes, unfortunately, are not quite adequate enough for all access to low-level resources. For example, there is no standardized URL implementation that may be used to access a resource that needs to be obtained from the classpath or relative to a ServletContext. While it is possible to register new handlers for specialized URL prefixes (similar to existing handlers for prefixes such as http:), this is generally quite complicated, and the URL interface still lacks some desirable functionality, such as a method to check for the existence of the resource being pointed to.
Spring’s Resource interface is meant to be a more capable interface for abstracting access to low-level resources.
在 Java 中,将不同来源的资源抽象成 java.net.URL ,即统一资源定位器(Uniform Resource Locator),然后通过注册不同的 handler ( URLStreamHandler )来处理不同来源的资源的读取逻辑,一般 handler 的类型使用不同前缀(协议, Protocol )来识别,如“file:”“http:” “jar:”等,然而 URL 没有默认定义相对 Classpath 或 ServletContext 等资源的 handler ,虽然可以注册自己的 URLStreamHandler 来解析特定的 URL 前缀(协议), 比如“classpath:”,然而这需要了解 Url 的实现机制,实现也比较复杂,而且 Url 也没有提供一些基本的方法,如检查当前资源是否存在、检查当前资源是否可读等方法。 因而 Spring 对其内部使用到的资源实现了自己的抽象结构 : Resource 接口封装底层资源,(《Spring源码深度解析 第二版》,略微修改)然后通过 ResourceLoader 接口来实现 Resource 的加载策略,也即是提供了统一的资源定义和资源加载策略的抽象。通过不同策略进行的所有资源加载,都可以返回统一的抽象给客户端,客户端对资源可以进行的操作,则由 Resource 接口进行界定,具体如何处理,则交由不同来源的资源实现类来实现。
简单总结:
- Resource:提供统一的资源定义抽象,界定了对资源可以进行的处理操作。
- 例如:文件资源( FileSystemResource ) 、 Classpath 资源( ClassPathResource )、 URL 资源( UrlResource )、 InputStream 资源( InputStreamResource ) 、Byte 数组( ByteArrayResource )等 。
- ResourceLoader:提供统一的资源加载策略抽象,返回统一的 Resource 资源抽象给客户端。
3、统一资源 Resource
org.springframework.core.io.Resource 为 Spring 框架用到的所有资源提供了一个统一的抽象接口,它继承了 org.springframework.core.io.InputStreamSource 接口,而 InputStreamSource 接口则用于将对应的资源封装为 Java 的标准 InputStream。Resource 接口是具体资源访问策略的抽象,也是所有资源访问类所实现的接口。 Resource 接口提供的通用方法如下(详情可参考 API 文档 or 源码注释):
public interface Resource extends InputStreamSource { /** * 检查资源是否物理形式实际存在 */ boolean exists(); /** * 资源是否可读取 */ default boolean isReadable() { return exists(); } /** * 资源文件是否打开状态,如果资源文件不能多次读取,每次读取结束应该显式关闭,以防止资源泄漏。 */ default boolean isOpen() { return false; } /** * 是否是文件系统中的文件 File */ default boolean isFile() { return false; } /** * 返回资源对应的 URL 句柄 */ URL getURL() throws IOException; /** * 返回资源对应的 URI 句柄 */ URI getURI() throws IOException; /** * 返回资源对应的 File 句柄 */ File getFile() throws IOException; /** * 返回 ReadableByteChannel */ default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } /** * 返回资源的内容长度 */ long contentLength() throws IOException; /** * 资源的最后修改时间 */ long lastModified() throws IOException; /** * 根据资源的相对路径创建对应的资源 */ Resource createRelative(String relativePath) throws IOException; /** * 资源的文件名,不带路径信息的文件名 */ @Nullable String getFilename(); /** * 资源的描述信息,用来在错误处理中打印信息 。 */ String getDescription(); }
另外就是继承的 InputStreamSource 接口的方法如下:
public interface InputStreamSource { /** * 返回底层资源的标准输入流。每次调用都返回新的输入流,调用者必须负责关闭输入流。 */ InputStream getInputStream() throws IOException; }
3.1、Resource 的类继承结构
Resource 的部分类继承结构如下图:
底层资源可能会有各种来源,像文件系统、Url、classpath,甚至是 servletcontext 等,因此,Resource 需要根据资源的不同类型提供不同的具体实现,如 文件( FileSystemResource ) 、 Classpath 资源( ClassPathResource )、 URL 资源( UrlResource )、 InputStream 资源( InputStreamResource ) 、Byte 数组( ByteArrayResource )等。上图只是展示了部分常见的实现类(继承结构图来自 IntelliJ IDEA,对着类右键 ==》Diagrams ==》 Show Diagram...,然后可以对着类图中的类右键 ==》 Show Implementation 展示实现类 或者 Show Parent 展示父类,还有其他的显示属性、方法等。),相关说明如下:
- ClassPathResource:class path 类型资源的封装实现类,内部使用给定的 ClassLoader 或者给定的 Class 进行资源加载。
- Resource implementation for class path resources. Uses either a given ClassLoader or a given Class for loading resources.
- Supports resolution as java.io.File if the class path resource resides in the file system, but not for resources in a JAR. Always supports resolution as URL.
- FileSystemResource:java.io.File 和 java.nio.file.Path 类型资源的封装实现类,用于处理文件系统资源。支持 File 和 Url 的形式。从 Spring Framework 5.0 开始使用 NIO2 进行 读/写 交互。从 5.1 开始,还可能是通过 Path 句柄来进行构造,这种场景下它将通过 NIO2进行所有的文件系统交互,只有通过 getFile() 时才转转为 File。
- Resource implementation for java.io.File and java.nio.file.Path handles with a file system target. Supports resolution as a File and also as a URL. Implements the extended WritableResource interface.
- Note: As of Spring Framework 5.0, this Resource implementation uses NIO.2 API for read/write interactions. As of 5.1, it may be constructed with a Path handle in which case it will perform all file system interactions via NIO.2, only resorting to File on getFile().
- ByteArrayResource:对 byte 数组的封装实现类,会根据给定的 byte 数组构造一个对应的 ByteArrayInputStream 作为 InputStream 类型的返回。
- Resource implementation for a given byte array.
- Creates a ByteArrayInputStream for the given byte array.
- Useful for loading content from any given byte array, without having to resort to a single-use InputStreamResource. Particularly useful for creating mail attachments from local content, where JavaMail needs to be able to read the stream multiple times.
- UrlResource:对 java.net.URL 类型资源的封装实现类。支持 URL 和 File(使用 file: 协议的时候)的形式
- Resource implementation for java.net.URL locators. Supports resolution as a URL and also as a File in case of the "file:" protocol.
- InputStreamResource:将给定的 InputStream 作为资源的封装实现类。只有当其他类型都无法使用的时候才会用到,尽量使用相匹配的类型进行处理。
- Resource implementation for a given InputStream.
- Should only be used if no other specific Resource implementation is applicable. In particular, prefer ByteArrayResource or any of the file-based Resource implementations where possible.
- In contrast to other Resource implementations, this is a descriptor for an already opened resource - therefore returning true from isOpen(). Do not use an InputStreamResource if you need to keep the resource descriptor somewhere, or if you need to read from a stream multiple times.
相关注释说明可以参考上面贴出来的英文,或者直接在源码中看相关的文档注释(其实也就是来自文档注释)。
3.2、AbstractResource
org.springframework.core.io.AbstractResource 是 Resource 接口的抽象子类,提供了大部分接口方法的典型预实现。exists 方法会检查相关的 File 或者 InputStream 能否打开;isOpen 方法则总是返回 false ;getURL 和 getFile 方法则默认直接抛出异常,这个需要具体的资源实现来进行判断,因为 AbstractResource 并不清楚具体的资源类型;toString 方法则返回对应的描述信息,也即是 getDescription()。
- Convenience base class for Resource implementations, pre-implementing typical behavior.
- The "exists" method will check whether a File or InputStream can be opened; "isOpen" will always return false; "getURL" and "getFile" throw an exception; and "toString" will return the description.
AbstractResource 中的具体是实现如下:
public abstract class AbstractResource implements Resource { /** * 检查文件是否存在,或者检查有对应的流 InputStream 存在,此时需要关闭流 */ @Override public boolean exists() { // Try file existence: can we find the file in the file system? // 先判断文件 File 是否存在:基于 File 的判断 if (isFile()) { try { return getFile().exists(); } catch (IOException ex) { Log logger = LogFactory.getLog(getClass()); if (logger.isDebugEnabled()) { logger.debug("Could not retrieve File for existence check of " + getDescription(), ex); } } } // Fall back to stream existence: can we open the stream? // 其次检查是否是可以打开的流 InputStream:基于 InputStream 的判断 try { getInputStream().close(); return true; } catch (Throwable ex) { Log logger = LogFactory.getLog(getClass()); if (logger.isDebugEnabled()) { logger.debug("Could not retrieve InputStream for existence check of " + getDescription(), ex); } return false; } } /** * 对于存在的资源,此实现总是返回true(从5.1版修订),通过 exists() 进行判断 */ @Override public boolean isReadable() { return exists(); } /** * 直接返回 false,表示未打开 */ @Override public boolean isOpen() { return false; } /** * 直接返回 false,表示不为 File,需要子类重写判断 */ @Override public boolean isFile() { return false; } /** * 直接抛出 FileNotFoundException 异常,需要子类实现 */ @Override public URL getURL() throws IOException { throw new FileNotFoundException(getDescription() + " cannot be resolved to URL"); } /** * 基于 getURL() 返回的 URL 构建 URI */ @Override public URI getURI() throws IOException { URL url = getURL(); try { return ResourceUtils.toURI(url); } catch (URISyntaxException ex) { throw new NestedIOException("Invalid URI [" + url + "]", ex); } } /** * 直接抛出 FileNotFoundException 异常,需要子类实现 */ @Override public File getFile() throws IOException { throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path"); } /** * 返回根据 getInputStream() 的结果构建的 ReadableByteChannel */ @Override public ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } /** * 获取资源的长度。这里是通过全部读取来计算资源的字节长度 */ @Override public long contentLength() throws IOException { InputStream is = getInputStream(); try { long size = 0; byte[] buf = new byte[256]; int read; while ((read = is.read(buf)) != -1) { size += read; } return size; } finally { try { is.close(); } catch (IOException ex) { Log logger = LogFactory.getLog(getClass()); if (logger.isDebugEnabled()) { logger.debug("Could not close content-length InputStream for " + getDescription(), ex); } } } } /** * 资源文件的最后的修改时间 */ @Override public long lastModified() throws IOException { File fileToCheck = getFileForLastModifiedCheck(); long lastModified = fileToCheck.lastModified(); if (lastModified == 0L && !fileToCheck.exists()) { throw new FileNotFoundException(getDescription() + " cannot be resolved in the file system for checking its last-modified timestamp"); } return lastModified; } /** * 返回相应的资源文件用于检查最后修改时间,内部直接使用 getFile() 实现 */ protected File getFileForLastModifiedCheck() throws IOException { return getFile(); } /** * 直接抛出 FileNotFoundException 异常,需要子类实现 */ @Override public Resource createRelative(String relativePath) throws IOException { throw new FileNotFoundException("Cannot create a relative resource for " + getDescription()); } /** * 获取资源名称,这里直接返回 null ,需要子类实现 */ @Override @Nullable public String getFilename() { return null; } @Override public boolean equals(@Nullable Object other) { return (this == other || (other instanceof Resource && ((Resource) other).getDescription().equals(getDescription()))); } @Override public int hashCode() { return getDescription().hashCode(); } /** * 这里获取资源的描述信息作为返回 */ @Override public String toString() { return getDescription(); } }
Spring 的很多顶层设计接口,都会有相关的 Abstract / Default 子类来处理一些典型的预实现,如果需要自定义这些接口的继承类,比如这里需要自定义的 Resource ,则建议直接继承 AbstractResource ,然后根据具体的资源特性重写相关的方法,而不是直接继承顶层接口 Resource,重写全部方法。
Spring 提供的资源具体实现类在上面已经简单介绍了下,这里就不一一具体说明,有兴趣的读者可以去翻一翻对应的源码进行研究。
4、统一资源加载策略 ResourceLoader
org.springframework.core.io.ResourceLoader 提供了资源加载策略的统一抽象,具体的资源加载则由对应的实现类来进行加载策略的实现。说是加载,其实理解为资源定位会更清晰点,也就是统一资源定位器(Uniform Resource Locator),ResourceLoader 其实就是根据相关的资源文件地址来定位所有的资源,并作为标准的 Resource 进行返回。
ResourceLoader 的内部接口定义如下:
public interface ResourceLoader { /** Pseudo URL prefix for loading from the class path: "classpath:". */ // 用于从类路径加载的伪URL前缀:“classpath:” String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; /** * 根据指定的资源路径返回对应的资源 Resource,该 Resource 句柄 * 应该总是一个可重用的资源描述符,允许多个 Resource.getInputStream()调用。 * 需要注意的是该 Resource 句柄 并不确保对应的资源一定存在,需要调用 * Resource.exists() 来进行实际的判断。 * 该方法支持以下这些模式的资源加载: * · 全限定路径 URL 位置的资源,如:"file:C:/test.dat". * · classpath 类路径位置的资源,如 "classpath:test.dat". * · 相对路径的资源,如 "WEB-INF/test.dat". 这种情况下会根据不同实现返回不同的 Resource 实例 */ Resource getResource(String location); /** * 返回当前 ResourceLoader 所用到的 ClassLoader , * 需要直接访问 ClassLoader 的客户端,可以通过 ResourceLoader 以这种统一的方式来直接获取 ClassLoader 。 * Resource 中的实现类 ClassPathResource 可以根据指定的 ClassLoader 进行资源加载 */ @Nullable ClassLoader getClassLoader(); }
4.1、ResourceLoader 的类继承结构
ResourceLoader 的部分类继承结构如下图:
下面分别通过 DefaultResourceLoader 和 ResourcePatternResolver 2个分支来进行详细介绍。
4.2、DefaultResourceLoader
Default 类与 Abstract 类有些相似,都是提供了接口的一些典型的预实现。org.springframework.core.io.DefaultResourceLoader 为 ResourceLoader 提供了默认实现。
4.2.1、DefaultResourceLoader 的内部属性
这里比较重要的是 classLoader 和 protocolResolvers,ClassLoader 是用于加载 classpath 下的资源的,而 ProtocolResolver 则是用户自定义的加载策略。接下来会进行相关介绍。
// 类加载器 @Nullable private ClassLoader classLoader; // 用户自定义的特定协议资源加载解析策略接口,用于自定义加载策略 private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4); // 各种类型资源的缓存 private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);
4.2.2、DefaultResourceLoader 的构造函数
DefaultResourceLoader 的构造函数也比较简单,一个不带参的空构造函数和一个指定 ClassLoader 的构造函数。
需要说明的是,ClassLoader 还可以通过 setClassLoader() 来进行指定,这里可以使用 ClassUtils.getDefaultClassLoader(),内部也是优先使用 Thread.currentThread().getContextClassLoader() 来获取,也即是执行线程的 ClassLoader。
/** * 无参构造函数,这里内部实际上就是优先使用 Thread.currentThread().getContextClassLoader() */ public DefaultResourceLoader() { this.classLoader = ClassUtils.getDefaultClassLoader(); } /** * 指定 ClassLoader 的带参构造函数 */ public DefaultResourceLoader(@Nullable ClassLoader classLoader) { this.classLoader = classLoader; } /** * 设置指定的 ClassLoader,也可以使用默认的 Thread.currentThread().getContextClassLoader() */ public void setClassLoader(@Nullable ClassLoader classLoader) { this.classLoader = classLoader; } @Override @Nullable public ClassLoader getClassLoader() { return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader()); }
4.2.3、ProtocolResolver 自定义的资源加载策略
org.springframework.core.io.ProtocolResolver 是特定协议的资源加载解析策略接口。用作 DefaultResourceLoader 的SPI,允许在不继承 DefaultResourceLoader(或 ApplicationContext 实现类)的情况下处理自定义协议。这样,如果需要自定义资源 Resource 和相应的加载策略,则可以通过继承 AbstractResource 来实现对应的资源,然后再增加相关的 ProtocolResolver 来实现对应的资源定位加载策略,这样就不需要再继承 DefaultResourceLoader 了。
ProtocolResolver 内部仅有一个用于资源定位解析的方法 Resource resolve(String location, ResourceLoader resourceLoader);
ProtocolResolver 整个接口代码如下:
/** * A resolution strategy for protocol-specific resource handles. * * <p>Used as an SPI for {@link DefaultResourceLoader}, allowing for * custom protocols to be handled without subclassing the loader * implementation (or application context implementation). * * <p>特定协议的资源加载解决策略接口。 * 用作 DefaultResourceLoader 的SPI,允许在不继承 DefaultResourceLoader(或 ApplicationContext 实现类)的情况下处理自定义协议。 * * @author Juergen Hoeller * @since 4.3 * @see DefaultResourceLoader#addProtocolResolver */ @FunctionalInterface public interface ProtocolResolver { /** * Resolve the given location against the given resource loader * if this implementation\'s protocol matches. * <p>根据指定的 ResourceLoader 来解析对应的资源路径,若成功则返回相应的 Resource * @param location the user-specified resource location 指定的资源路径 * @param resourceLoader the associated resource loader 指定的 ResourceLoader * @return a corresponding {@code Resource} handle if the given location * matches this resolver\'s protocol, or {@code null} otherwise */ @Nullable Resource resolve(String location, ResourceLoader resourceLoader); }
Spring 并没有 ProtocolResolver 接口的任何实现类,这个完全需要用户自己进行定义实现,然后再通过 DefaultResourceLoader.addProtocolResolver(ProtocolResolver resolver) 注册到 Spring 中,该方法代码如下:
/** * Register the given resolver with this resource loader, allowing for * additional protocols to be handled. * <p>Any such resolver will be invoked ahead of this loader\'s standard * resolution rules. It may therefore also override any default rules. * <p>注册自定义的特定协议资源加载解决器,允许处理其他协议的资源 * 需要注意的是这些解析器在资源加载时会先执行,因此可能会覆盖其他默认的加载规则 * @since 4.3 * @see #getProtocolResolvers() */ public void addProtocolResolver(ProtocolResolver resolver) { Assert.notNull(resolver, "ProtocolResolver must not be null"); this.protocolResolvers.add(resolver); }
4.2.4、getResource 方法
接下来看看核心方法 getResource(String location) 的具体实现,它会根据提供的 location 返回对应的 Resource。DefaultResourceLoader 的子类 ClassRelativeResourceLoader 和 FileSystemResourceLoader 并没有覆盖这个方法,因此 ResourceLoader 的资源加载策略就是依靠 DefaultResourceLoader 来实现的,具体实现如下:
@Override public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); // 首先,先使用自定义的加载解析策略 ProtocolResolver 来加载资源,解析得到就直接返回相应资源 for (ProtocolResolver protocolResolver : getProtocolResolvers()) { 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()); } else { try { // 判断是否为文件 Url,是则返回 FileUrlResource 类型的资源;否则返回 UrlResource 类型的资源 //以上是关于Spring5源码分析(005)——IoC篇之统一资源加载Resource和ResourceLoader的主要内容,如果未能解决你的问题,请参考以下文章Spring5源码分析(006)——IoC篇之核心类DefaultListableBeanFactory和XmlBeanDefinitionReader
Spring5源码分析(007)——IoC篇之加载 BeanDefinition(的大致流程)