zoukankan      html  css  js  c++  java
  • Spring5源码分析(005)——IoC篇之统一资源加载Resource和ResourceLoader

     注:《Spring5源码分析》汇总可参考:Spring5源码分析(002)——博客汇总 


    本文主要介绍 Spring 的统一资源 Resource 及其加载策略 ResourceLoader,目录如下:

    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 类型的资源
                // 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) {
                // 最后,则返回 ClassPathContextResource 类型的资源
                // No URL -> resolve as resource path.
                return getResourceByPath(location);
            }
        }
    }

       其中 getResourceByPath(String path) 方法如下:

    /**
     * 默认直接返回 ClassPathContextResource,子类可以覆盖重写
     */
    protected Resource getResourceByPath(String path) {
        return new ClassPathContextResource(path, getClassLoader());
    }

       将注释整理下来,看下一个完整的加载定位策略:

    • 首先,使用自定义的加载解析策略 ProtocolResolver 来加载资源,解析得到就直接返回相应资源
    • 然后,以 / 开头的资源,则调用 getResourceByPath(String path) 返回 ClassPathContextResource 类型的资源
    • 之后,以 classpath: 开头的,返回 ClassPathResource 类型的资源
    • 接着,判断是否为文件 Url,是则返回 FileUrlResource 类型的资源;否则返回 UrlResource 类型的资源
    • 最后,若出现了 MalformedURLException 异常,则还是委派 getResourceByPath(String path) 返回 ClassPathContextResource 类型的资源。这样就基本完成了资源的定位加载。

    4.2.5、示例

    (这一段是参考的)DefaultResourceLoader 加载资源的具体策略演示代码(《Spring 揭秘》 P89):

    ResourceLoader resourceLoader = new DefaultResourceLoader();
    
    Resource fileResource1 = resourceLoader.getResource("D:/Users/test/Documents/spring.txt");
    System.out.println("fileResource1 is FileSystemResource:" + (fileResource1 instanceof FileSystemResource));
    
    Resource fileResource2 = resourceLoader.getResource("/Users/test/Documents/spring.txt");
    System.out.println("fileResource2 is ClassPathResource:" + (fileResource2 instanceof ClassPathResource));
    
    Resource urlResource1 = resourceLoader.getResource("file:/Users/test/Documents/spring.txt");
    System.out.println("urlResource1 is UrlResource:" + (urlResource1 instanceof UrlResource));
    
    Resource urlResource2 = resourceLoader.getResource("http://www.baidu.com");
    System.out.println("urlResource1 is urlResource:" + (urlResource2 instanceof UrlResource));

       运行结果:

    fileResource1 is FileSystemResource:false
    fileResource2 is ClassPathResource:true
    urlResource1 is UrlResource:true
    urlResource1 is urlResource:true
    • 对于 fileResource1 的结果,可能会有点奇怪,为何是 ClassPathResource ,而不是 FileSystemResource 类型的资源?DefaultResourceLoader.getResource(String location) 在解析 "D:/Users/test/Documents/spring.txt" 的过程中没有遇到匹配的,于是抛出了 MalformedURLException,然后通过 getResourceByPath(String path) 返回默认的 ClassPathResource 类型的资源。
    • urlResource1 和 urlResource2 的协议就比较清晰了,通过 URL 来进行定义,返回的都是 UrlResource 类型资源。

    4.3、FileSystemResourceLoader

      通过上面的示例,我们可以看到,如果是文件系统的文件资源,DefaultResourceLoader.getResourceByPath(String path) 的处理是不恰当的。这个时候就需要使用 org.springframework.core.io.FileSystemResourceLoader 了,它重写了 DefaultResourceLoader.getResourceByPath(String path) 方法,返回 FileSystemResource 类型的资源以便可以从文件系统进行加载,最终得到我们需要的资源类型: 

    public class FileSystemResourceLoader extends DefaultResourceLoader {
    
        /**
         * 返回 FileSystemContextResource 类型的资源
         * 将资源路径解析为文件系统路径,以 / 开头的会被解析为 VM 当前工作目录的相对路径
         */
        @Override
        protected Resource getResourceByPath(String path) {
            // 截取开头的 /,会当作 context 上下文的额相对路径
              if (path.startsWith("/")) {
                path = path.substring(1);
            }
            return new FileSystemContextResource(path);
        }
    
    
        /**
         * 通过实现 ContextResource 接口显式地表示上下文相关路径的 FileSystemResource
         */
        private static class FileSystemContextResource extends FileSystemResource implements ContextResource {
    
            public FileSystemContextResource(String path) {
                super(path);
            }
    
            // 返回文件资源的路径
            @Override
            public String getPathWithinContext() {
                return getPath();
            }
        }
    
    }

        这里的 FileSystemContextResource 是 FileSystemResourceLoader 的内部类,它继承了 FileSystemResource 类,还实现了 ContextResource 接口,既可以表示实际文件系统的资源,也可以表示 context 环境相对路径的资源。FileSystemContextResource 的构造函数也比较简单,直接调用父类 FileSystemResourceLoader 的构造函来实现。

      4.2.5 的示例中,如果使用 FileSystemResourceLoader 进行加载,则可以得到 FileSystemResource 类型资源的 fileResource1 。

    4.4、ClassRelativeResourceLoader

      org.springframework.core.io.ClassRelativeResourceLoader 是 DefaultResourceLoader 的另一个子类: 

    /**
     * ResourceLoader 的实现类,将普通的资源路径解析为给定 java.lang.Class 相关的资源,
     * 用于解析加载 Class 所在包或所在包的子包下的资源
     */
    public class ClassRelativeResourceLoader extends DefaultResourceLoader {
    
        private final Class<?> clazz;
    
    
        /**
         * 根据指定的 class 创建 ClassRelativeResourceLoader 实例,
         * 内部会通过 class 来获取对应的 ClassLoader,这个 ClassLoader 可以用于
         * 自定义解析策略的加载,也可以用于 ClassPathResource 类型的资源(协议是 classpath:),
         * 参考 DefaultResourceLoader.getResource()
         */
        public ClassRelativeResourceLoader(Class<?> clazz) {
            Assert.notNull(clazz, "Class must not be null");
            this.clazz = clazz;
            setClassLoader(clazz.getClassLoader());
        }
    
        // 返回 ClassRelativeContextResource 类型的资源
        @Override
        protected Resource getResourceByPath(String path) {
            return new ClassRelativeContextResource(path, this.clazz);
        }
    
    
        /**
         * 通过实现 ContextResource 接口显式地表示上下文相关路径的 ClassRelativeContextResource
         */
        private static class ClassRelativeContextResource extends ClassPathResource implements ContextResource {
    
            private final Class<?> clazz;
    
            public ClassRelativeContextResource(String path, Class<?> clazz) {
                super(path, clazz);
                this.clazz = clazz;
            }
    
            // 返回文件资源的路径
            @Override
            public String getPathWithinContext() {
                return getPath();
            }
    
            // 根据资源的相对路径创建对应的资源,内部会将相对路径拼接进去
            @Override
            public Resource createRelative(String relativePath) {
                String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
                return new ClassRelativeContextResource(pathToUse, this.clazz);
            }
        }
    
    }

    4.5、ResourcePatternResolver

      ResourceLoader 的 Resource getResource(String location) 方法每次只返回一个 Resource,无法加载多个资源,所以一般会是对应到具体的某个资源文件。如果需要加载多个资源的,则需要用 ResourceLoader 的另一个扩展类 org.springframework.core.io.support.ResourcePatternResolver,它支持根据指定的资源路径一次返回多个 Resource 实例对象: 

    public interface ResourcePatternResolver extends ResourceLoader {
    
        String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
    
        /**
         * 返回指定路径的 Resource 数组实例对象
         */
        Resource[] getResources(String locationPattern) throws IOException;
    
    }

       ResourcePatternResolver 增加了一种伪 URL 协议:“classpath*:”,用于解析加载从当前 classpath 下以及所有 jar 包中的相关路径的资源 Resource;而新增加的 Resource[] getResources(String locationPattern) 则可以根据指定的路径同时返回多个 Resource 实例。

    4.6、PathMatchingResourcePatternResolver

      来到重点了,前面看到过 ApplicationContext 是通过 ResourcePatternResolver 来获取到资源的,再具体一点,其实是它的实现类 org.springframework.core.io.support.PathMatchingResourcePatternResolver,除了支持 ResourceLoader ,以及 ResourcePatternResolver 新增的 “classpath*:” 协议外,还支持 Ant 风格的路径匹配模式,例如常见的 **/*.xml 。相对地,PathMatchingResourcePatternResolver 的实现也较前面介绍的复杂一些。

    4.6.1、构造函数和属性

      PathMatchingResourcePatternResolver 提供了3个构造函数,都与 ResourceLoader 有关:

    • 实例化时需要指定 ResourceLoader,默认是 DefaultResourceLoader。
    • PathMatcher pathMatcher 属性,默认是 AntPathMatcher,Ant 类型的路径匹配实现类 
    /**
     * 内置的 ResourceLoader 资源加载定位器
     */
    private final ResourceLoader resourceLoader;
    /**
     * Ant 路径匹配器,用于支持 Ant 类型的路径匹配
     */
    private PathMatcher pathMatcher = new AntPathMatcher();
    
    /**
     * 没有指定的话就是用默认的 DefaultResourceLoader
     */
    public PathMatchingResourcePatternResolver() {
        this.resourceLoader = new DefaultResourceLoader();
    }
    
    public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
        Assert.notNull(resourceLoader, "ResourceLoader must not be null");
        this.resourceLoader = resourceLoader;
    }
    
    public PathMatchingResourcePatternResolver(@Nullable ClassLoader classLoader) {
        this.resourceLoader = new DefaultResourceLoader(classLoader);
    }

    4.6.2、getResource 方法

      getResource 直接通过委托给 ResourceLoader 来进行加载,也即是默认的 DefaultResourceLoader 进行加载: 

    @Override
    @Nullable
    public ClassLoader getClassLoader() {
        return getResourceLoader().getClassLoader();
    }
    
    // 使用内置的 ResourceLoader 进行加载,如果不是 ant 风格的路径,应该都是单一的 Resource
    @Override
    public Resource getResource(String location) {
        return getResourceLoader().getResource(location);
    }

    4.6.3、getResources 方法

      getResources 方法用于加载并返回多个资源,Ant 风格匹配加载的就是这个方法在执行的(比较常见的就是各种带 * 的路径匹配)。

    @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()));
            }
        }// 不以 "classpath*:" 开头的路径
        else {
            // Generally only look for a pattern after a prefix here,
            // and on Tomcat only after the "*/" separator for its "war:" protocol.
            // 通常只在这里查找前缀后的模式,而在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)};
            }
        }
    }

       大体逻辑不复杂:

    • 路径包含通配符的,不管有没有 "classpath*:" 开头的,都调用 findPathMatchingResources(String locationPattern) 进行多个资源的加载;
    • 以 "classpath*:" 开头,但路径不包含通配符的,则调用 findAllClassPathResources(String location) 进行加载
    • 不是 "classpath*:" 开头,也不包含通配符,那么理论上应该是某个资源路径,直接使用 ResourceLoader 来进行加载。

      下面将对 findPathMatchingResources(String locationPattern) 和 findAllClassPathResources(String location) 进行分析。

    4.6.4、findAllClassPathResources 方法

      以 "classpath*:" 开头,但路径不包含通配符的,则调用 findAllClassPathResources(String location) 进行加载,该方法返回 classes 路径下和所有 jar 包中与路径匹配的所有资源。

      这种情况下比较常见的是不同的 jar 包但是相同路径的配置文件,例如,当前项目有个 /spring/app-service.xml,需要引用内部团队其他关联依赖 B.jar ,而且 B.jar 中也有同名的配置文件 /spring/app-service.xml,如果需要都加载,那就可以使用 classpath*:/spring/app-service.xml 来进行配置和加载。

      findAllClassPathResources(String location) 的内部实现如下: 

    /**
     * 通过类加载器找到 classes 路径和所有 jar 包中与给定路径相匹配的资源,
     * 委托给 doFindAllClassPathResources(String) 进行实际的处理
     */
    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]);
    }
    
    /**
     * <p>通过类加载器找到 classes 路径和所有 jar 包中与给定路径相匹配的资源
     */
    protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
        Set<Resource> result = new LinkedHashSet<>(16);
        ClassLoader cl = getClassLoader();
        // 1. 通过 ClassLoader 加载指定路径下的所有资源
        Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
        // 2.
        while (resourceUrls.hasMoreElements()) {
            URL url = resourceUrls.nextElement();
            // 将 URL 转换成对应的 UrlResource
            result.add(convertClassLoaderURL(url));
        }
        // 3. 加载路径下所有的 jar 包
        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...
            addAllClassLoaderJarRoots(cl, result);
        }
        return result;
    }
    
    /**
     * 将 ClassLoader 返回的给定 URL 转换成 Resource,默认是 UrlResource
     */
    protected Resource convertClassLoaderURL(URL url) {
        return new UrlResource(url);
    }

       实际上执行加载的是 doFindAllClassPathResources(String path),处理过程如下:

    • 1. 通过 ClassLoader 进行资源的加载,如果实例化 PathMatchingResourcePatternResolver 时已指定了 ResourceLoader,则使用这个ResourceLoader 的 ClassLoader 作为委托对象,通过调用其 getResources(String name) 方法来进行处理,否则通过 ClassLoader.getSystemResources(String name) 来进行处理。ClassLoader.getResources(String name) 的代码实现如下: 
    public Enumeration<URL> getResources(String name) throws IOException {
        @SuppressWarnings("unchecked")
        Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
        if (parent != null) {
            tmp[0] = parent.getResources(name);
        } else {
            tmp[0] = getBootstrapResources(name);
        }
        tmp[1] = findResources(name);
    
        return new CompoundEnumeration<>(tmp);
    }

       这里比较明显,如果 ClassLoader 有父类加载器,则通过父类加载器迭代加载获取资源,直至最后则是调用 getBootstrapResources(name) 进行资源获取。

    • 2. 迭代遍历 1 中获取到的 URL 集合资源,通过 convertClassLoaderURL(URL url) 将 URL 转换成 UrlResource 实例对象。
    • 3. 如果路径 path 为""(classpath*: 或者 classpath*:/),则通过 addAllClassLoaderJarRoots(@Nullable ClassLoader classLoader, Set<Resource> result) 方法加载路径下所有 jar 包的资源。有兴趣的可以继续跟进看看。

      综上,findAllClassPathResources(String location) 方法其实就是利用 ClassLoader 来加载指定路径下的资源,包括 classes 路径下和 jar 包的资源。如果传入的路径为空或者是 / (classpath*: 或者 classpath*:/),则会通过 addAllClassLoaderJarRoots 方法来加载所有的 jar 包的资源。

    4.6.5、findPathMatchingResources 方法

      路径中包含通配符的,都是使用这个方法进行资源的加载: 

    /**
     * <通过 Ant 风格的 PathMatcher 来查找匹配给定路径的所有资源,
     * 支持 在 jar 包、zip 文件、和文件系统中查找相关资源
     */
    protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
        // 确定根路径和子路径
        String rootDirPath = determineRootDir(locationPattern);
        String subPattern = locationPattern.substring(rootDirPath.length());
        // 获取根路径下的资源
        Resource[] rootDirResources = getResources(rootDirPath);
        Set<Resource> result = new LinkedHashSet<>(16);
        // 迭代遍历
        for (Resource rootDirResource : rootDirResources) {
            rootDirResource = resolveRootDirResource(rootDirResource);
            URL rootDirUrl = rootDirResource.getURL();
            // bundle 类型的资源
            if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
                URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
                if (resolvedUrl != null) {
                    rootDirUrl = resolvedUrl;
                }
                rootDirResource = new UrlResource(rootDirUrl);
            }
            // vfs 类型的资源
            if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
                result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
            }
            // jar 类型的资源
            else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
                result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
            }
            // 其他资源类型
            else {
                result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
            }
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
        }
        // 转成 Resource 数组
        return result.toArray(new Resource[0]);
    }

       这里加载主要分为2步:

    • 确定根目录(通配符之前目录),获取该目录下的所有资源
    • 然后进行迭代匹配获取所有需要的资源。

      根据常见的场景,我们比较关注的是以下这2个(类)方法:

    • determineRootDir(String location)
    • doFindPathMatchingXxxResources
    4.6.5.1、determineRootDir 方法

      determineRootDir 方法主要用于确认根路径: 

    /**
     * <p>确定指定路径的根路径
     */
    protected String determineRootDir(String location) {
        // 冒号之后一位,即协议后面的位置
        int prefixEnd = location.indexOf(':') + 1;
        // 目录结束位置
        int rootDirEnd = location.length();
        // 循环判断是否有分隔符,如果有,则截断最后一个 / 之后的部分
        // 例如 classpath*:spring/*-service.xml,循环判断后剩下 spring/ 已经不包含通配符了
        // 如果是 classpath*:*-service.xml,则会出现 rootDirEnd == 0 的情况
        while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
            // 通配符至少占 1 个,再加上分隔符,所以 -2;最后 +1 表示返回结果要包含分隔符
            rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
        }
        // 将 prefixEnd 的值赋给 rootDirEnd,也即是冒号后一位
        if (rootDirEnd == 0) {
            rootDirEnd = prefixEnd;
        }
        // 截取根目录
        return location.substring(0, rootDirEnd);
    }

       看看几个例子就可以大概知道这方法的作用了:

    原路径 根路径
    classpath*:spring/a*/*-service.xml classpath*:spring/
    classpath*:spring/a/*-service.xml classpath*:spring/a/
    classpath*:*-service.xml classpath*:
    4.6.5.2、doFindPathMatchingXxxResources

      这个指的是特定类型资源的全部加载,只要是 findPathMatchingResources 方法中用到的这3个

    • doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern)
    • doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
    • VfsResourceMatchingDelegate.findMatchingResources(URL rootDirURL, String locationPattern, PathMatcher pathMatcher)

      对于这几个方法的分析,可以参考以下文章:

    5、总结

      行文至此,Spring 的资源加载策略和加载过程已基本分析完毕,相信读者也知道了资源比较常见的用途(初始化 Application),以及平常在配置中为何可以写各种带 * 的配置了。Spring 的资源加载策略和加载过程总结如下:

    • Spring 提供了 Resource 和 ResourceLoader 来进行资源的统一抽象及其统一定位加载,并且提供了合适的 Default 类或者 Abstract 类来处理一些比较常用的预实现,同时使得自定义实现更加清晰便捷。
    • AbstractResource 作为 Resource 的默认抽象实现,提供了大部分接口方法的典型预实现,子类继承该类后只需要根据特定的资源特性覆盖相应的方法即可;对于自定义的 Resource 我们也是继承这个抽象类,然后根据实际来覆盖相关方法。
    • Spring 提供了丰富的资源实现类,如 文件( FileSystemResource ) 、 Classpath 资源( ClassPathResource )、 URL 资源( UrlResource )、 InputStream 资源( InputStreamResource ) 、Byte 数组( ByteArrayResource )等,用于表示各种底层资源。
    • DefaultResourceLoader 则提供了 ResourceLoader 的默认实现,可用于默认情况下的资源加载。自定义加载策略,除了直接继承 ResourceLoader 外,还可以直接通过实现 ProtocolResolver 接口,只需要提供自定义的资源加载策略。
    • Spring 还提供了 ResourcePatternResolver 接口,内部的 Resource[] getResources(String locationPattern) 方法可用于一次性返回多个资源,弥补了 DefaultResourceLoader 每次只能返回单一资源的不足,其实现类 PathMatchingResourcePatternResolver 既实现了 Resource getResource(String location) 方法,也实现了 Resource[] getResources(String locationPattern) 方法,是 ApplicationContext 的资源默认加载策略类。
    • Resource 和 ResourceLoader 相关接口和实现类都是在 spring-core 包里面,是比较核心的基础功能。

    6、测试案例参考

      spring-core 模块的 test 案例中的 org.springframework.core.io.support.PathMatchingResourcePatternResolverTests 中包含了一些相关的测试案例,可以进行参考,有兴趣的也可自行测试验证。

      另外,本文源码相关注释可参考github:https://github.com/wpbxin/spring-framework

    7、参考

    8、声明

      本文仅用于学习使用,如有雷同,纯属借鉴,如有问题,可留言反馈~~~

     

  • 相关阅读:
    【UML九种图系列】之用例图
    数据结构之后缀数组
    Web Service实例——天气预报
    庞果网 合法字符串
    web 版发送邮件-已删除
    C语言实现双向链表删除节点、插入节点、双向输出等操作
    [置顶] String StringBuffer StringBuilder的区别剖析
    N!末尾有多少个零
    细说业务逻辑 -- 丢失的业务逻辑层
    你真的了解分层架构吗?——写给被PetShop"毒害"的朋友们
  • 原文地址:https://www.cnblogs.com/wpbxin/p/13061470.html
Copyright © 2011-2022 走看看