1. 简介

在JDK中 java.net.URL 适用于加载资源的类,但是 URL 的实现类都是访问网络资源的,并没有可以从类路径或者相对路径获取文件及 ServletContext , 虽然可以通过自定义扩展URL接口来实现新的处理程序,但是这是非常复杂的,同时 URL 接口中定义的行为也并不是很理想的 ,如检测资源的存在等的行为,这也是 spring 为什么自己全新的开发一套自己的资源加载策略, 同时它也满足下面的特点:

  • 单一职责原则,将资源的定义和资源的加载的模型界限划的非常清晰
  • 采用高度抽象,统一的资源定义和资源加载的策略和行为,资源加载返回给客户端的是抽象的资源,客户端根据资源的行为定义对其进行具体化的处理

2. Resource 接口

spring 中的 Resource 接口目的在于成为一种功能更加强大的接口,用于抽象化对具体资源的访问,它继承了 org.springframework.core.io.InputStreamSource 接口,作为资源定义的顶级接口, Resource 内部定义了通用的方法,并且有它的子类 AbstractResource 来提供统一的默认实现,

Resouerce 接口定义:

  1. //资源定义接口
  2. public interface Resource extends InputStreamSource {
  3. /**
  4. * 检验资源是否是物理存在
  5. */
  6. boolean exists();
  7. /**
  8. * 判断资源是否是可读的
  9. */
  10. default boolean isReadable() {
  11. return exists();
  12. }
  13. /**
  14. * 判断资源是否是打开的,true为打开
  15. */
  16. default boolean isOpen() {
  17. return false;
  18. }
  19. /**
  20. * 判断该资源是否是文件 true为是
  21. */
  22. default boolean isFile() {
  23. return false;
  24. }
  25. /**
  26. * 返回该资源的URL句柄
  27. */
  28. URL getURL() throws IOException;
  29. /**
  30. * 返回该资源的URI句柄
  31. */
  32. URI getURI() throws IOException;
  33. /**
  34. * 获取该资源的File句柄
  35. */
  36. File getFile() throws IOException;
  37. /**
  38. * 返回一个ReadableByteChannel 作为NIO中的可读通道
  39. */
  40. default ReadableByteChannel readableChannel() throws IOException {
  41. return Channels.newChannel(getInputStream());
  42. }
  43. /**
  44. * 获取资源内容的长度
  45. */
  46. long contentLength() throws IOException;
  47. /**
  48. * 返回该资源最后修改的时间戳
  49. */
  50. long lastModified() throws IOException;
  51. /**
  52. * 根据该资源的相对路径创建新资源
  53. */
  54. Resource createRelative(String relativePath) throws IOException;
  55. /**
  56. * 返回该资源的名称
  57. */
  58. @Nullable
  59. String getFilename();
  60. /**
  61. * 返回该资源的描述
  62. */
  63. String getDescription();
  64. }

InputStreamSource 接口定义:

  1. public interface InputStreamSource {
  2. /**
  3. * Return an {@link InputStream} for the content of an underlying resource.
  4. * <p>It is expected that each call creates a <i>fresh</i> stream.
  5. * <p>This requirement is particularly important when you consider an API such
  6. * as JavaMail, which needs to be able to read the stream multiple times when
  7. * creating mail attachments. For such a use case, it is <i>required</i>
  8. * that each {@code getInputStream()} call returns a fresh stream.
  9. * @return the input stream for the underlying resource (must not be {@code null})
  10. * @throws java.io.FileNotFoundException if the underlying resource doesn't exist
  11. * @throws IOException if the content stream could not be opened
  12. */
  13. InputStream getInputStream() throws IOException;
  14. }

Resource 中一些最重要的方法:

  • getInputStream() :找到并打开资源,并返回一个资源以 InputStream 供读取,每次调用都会返回一个新的 InputStream ,调用者有责任关闭流

  • exists() :返回 boolean 指示此资源是否实际以物理形式存在。

  • isOpen() :返回, boolean 指示此资源是否表示具有打开流的句柄, 如果为 trueInputStream 则不能多次读取,必须只读取一次,然后将其关闭以避免资源泄漏。返回 false 所有常用资源实现(除外) InputStreamResource 可读

  • getDescription() :返回对此资源的描述,以便在使用该资源时用于错误输出。这通常是标准文件名或资源的实际 URL

** Resource 实现**

  • UrlResource : 包装一个 java.net.URL ,可用于访问通常可以通过 URL 访问的任何对象,例如文件, HTTP 目标, FTP 目标等。所有 URL 都有一个标准化的 String 表示形式,因此适当的标准化前缀可用于指示另一种 URL 类型。如: file : 访问文件系统路径, http : 通过 HTTP 协议 ftp : 访问资源,通过 FTP 访问资源等

  • ClassPathResource : 此类表示应从类路径获取的资源。它使用线程上下文类加载器( ClassLoader ),给定的类加载器或给定的类来加载资源

  • FileSystemResource : 是一个 Resource 执行 java.io.Filejava.nio.file.Path 类型资源的封装,它支持 FileURL , 实现 WritableResource 接口,且从 Spring Framework 5.0 开始, FileSystemResource 使用 NIO2 API 进行读/写交互

  • ServletContextResource : 该 ServletContex t资源解释相关 Web 应用程序的根目录内的相对路径。

  • InputStreamResource : 将给定的 InputStream 作为一种资源的 Resource 的实现类

  • ByteArrayResource : 这是Resource给定字节数组的实现。它为给定的字节数组创建一个 ByteArrayInputStream

3. ResourceLoader 接口

ResourceLoader 主要是用于返回(即加载) Resource 对象,主要定义:

  1. public interface ResourceLoader {
  2. /** Pseudo URL prefix for loading from the class path: "classpath:". */
  3. String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
  4. /**
  5. * 返回指定路径的资源处理器
  6. * 必须支持完全限定的网址: "file:C:/test.dat"
  7. * 必须支持ClassPath 的 URL :"classpath:test.dat"
  8. * 必须支持相对路径 : "WEB-INF/test.dat"
  9. * 并不能保证资源是否物理存在,需要自己去检测通过existence
  10. * 再spring中所有的应用上下文都去实现这个接口,可以进行资源的加载
  11. */
  12. Resource getResource(String location);
  13. /**
  14. * 返回当前类的 ClassLoader 对象
  15. */
  16. @Nullable
  17. ClassLoader getClassLoader();
  18. }
  • 应用上下文即容器都有实现 ResourceLoader 这个接口,所有的上下文都可以用于获取 Resource 实例对象

  • 我们可以在特定的应用上下文中通过 getResource() 来获取特定类型的 Resource 实例,但是的保证 location 路径没有特殊的前缀,如 classpatch: 等,如果有特定前缀慢么会强制使用相应的资源类型,与上下文无关。

Prefix Example Explanation
classpath: classpath:com/myapp/config.xml 从类路径加载
file: file:///data/config.xml 从文件系统作为 URL 加载
http: https://myserver/logo.png 按照URL形式加载
(none) /data/config.xml 取决于应用上下文

ResourceLoader 的子类结构:

3.1 DefaultResourceLoader

这个类是 ResourceLoader 的默认实现类,与 Resource 接口的 AbstractResource 一样,

3.1.1. 构造函数

  • 提供有参和无参的构造函数,有参构造函数接受 ClassLoader 类型,如不带参数则使用默认的 ClassLoader , Thread.currentThread()#getContextClassLoader()

核心代码代码,部分省去:

  1. public class DefaultResourceLoader implements ResourceLoader {
  2. @Nullable
  3. private ClassLoader classLoader;
  4. private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
  5. private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);
  6. /**
  7. * 无参构造函数
  8. * @see java.lang.Thread#getContextClassLoader()
  9. */
  10. public DefaultResourceLoader() {
  11. this.classLoader = ClassUtils.getDefaultClassLoader();
  12. }
  13. /**
  14. * 带ClassLoader的有参构造函数
  15. */
  16. public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
  17. this.classLoader = classLoader;
  18. }
  19. /**
  20. * 设置 ClassLoader
  21. */
  22. public void setClassLoader(@Nullable ClassLoader classLoader) {
  23. this.classLoader = classLoader;
  24. }
  25. /**
  26. * Return the ClassLoader to load class path resources with.、
  27. * @see ClassPathResource
  28. */
  29. @Override
  30. @Nullable
  31. public ClassLoader getClassLoader() {
  32. return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
  33. }
  34. /**
  35. * Obtain a cache for the given value type, keyed by {@link Resource}.
  36. * @param valueType the value type, e.g. an ASM {@code MetadataReader}
  37. * @return the cache {@link Map}, shared at the {@code ResourceLoader} level
  38. * @since 5.0
  39. */
  40. @SuppressWarnings("unchecked")
  41. public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
  42. return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());
  43. }
  44. /**
  45. * Clear all resource caches in this resource loader.
  46. * @since 5.0
  47. * @see #getResourceCache
  48. */
  49. public void clearResourceCaches() {
  50. this.resourceCaches.clear();
  51. }
  52. }

3.1.2 getResource() 核心方法

ResourceLoader 中最核心的方法,他根据传入的 location 来返回相应的Resource,而 DefaultResourceLoader 对其做了核心实现, 子类都没覆盖该方法,所以我们可以断定 ResourceLoader 加载资源的核心策略都在 DefaultResourceLoader

核心代码:

  1. //DefaultResourceLoader.java
  2. @Override
  3. public Resource getResource(String location) {
  4. Assert.notNull(location, "Location must not be null");
  5. //1. 通过 ProtocolResolver 协议解析器来记载资源
  6. for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
  7. Resource resource = protocolResolver.resolve(location, this);
  8. if (resource != null) {
  9. return resource;
  10. }
  11. }
  12. //2.如果location是以 / 开头则返回 ClassPathContextResource 类型的 资源
  13. if (location.startsWith("/")) {
  14. return getResourceByPath(location);
  15. }//3.如果是以 classpath: 开头,则返回 ClassPathResource 类型的资源
  16. else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
  17. return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
  18. }
  19. else {
  20. try {
  21. //4.如果不是以上两种,则判断是否是 File URL ,如果是返回FileUrlResource 否则 返回UrlResource
  22. // Try to parse the location as a URL...
  23. URL url = new URL(location);
  24. return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
  25. }
  26. catch (MalformedURLException ex) {
  27. // No URL -> resolve as resource path.
  28. //5.最后则返回ClassPathContextResource
  29. return getResourceByPath(location);
  30. }
  31. }
  32. }

上述代码中具体说明了执行的流程,其中 getResourceByPath(location) 的具体实现代码如下:

  1. protected Resource getResourceByPath(String path) {
  2. return new ClassPathContextResource(path, getClassLoader());
  3. }

3.1.3 ProtocolResolver

全限定类名: org.springframework.core.io.ProtocolResolver ,是一个接口,用于用户自定义协议资源解析策略,是 DefaultResourceLoaderSPI ,允许处理自定义协议而无需将加载程序实现(或应用程序上下文实现)为子类,即不需要继承 ResourceLoader 的子类 DefaultResourceLoader , 而直接实现 ProtocolResolver 接口就可以自定义 ResourceLoader

  1. @FunctionalInterface
  2. public interface ProtocolResolver {
  3. /**
  4. * 使用指定的ResourceLoader 来解析location路径的 资源
  5. * Resolve the given location against the given resource loader
  6. * if this implementation's protocol matches.
  7. * @param location the user-specified resource location
  8. * @param resourceLoader the associated resource loader
  9. * @return a corresponding {@code Resource} handle if the given location
  10. * matches this resolver's protocol, or {@code null} otherwise
  11. */
  12. @Nullable
  13. Resource resolve(String location, ResourceLoader resourceLoader);
  14. }

在spring中该类并没有任何实现类,他需要用户自己实现,那么自定义的 ProtocolResolver 如何加载到spring中呢?在我们 DefaultResourceLoader 类中有一个方法 addProtocolResolver(ProtocolResolver resolver) 则是用来添加的

  1. /**
  2. * Register the given resolver with this resource loader, allowing for
  3. * additional protocols to be handled.
  4. * <p>Any such resolver will be invoked ahead of this loader's standard
  5. * resolution rules. It may therefore also override any default rules.
  6. * @since 4.3
  7. * @see #getProtocolResolvers()
  8. */
  9. public void addProtocolResolver(ProtocolResolver resolver) {
  10. Assert.notNull(resolver, "ProtocolResolver must not be null");
  11. this.protocolResolvers.add(resolver);
  12. }

3.2 FileSystemResourceLoader

DefaultResourceLoadergetResourceByPath() 方法的处理是直接返回了一个 ClassPathContextResource 类型的资源,这其实是不完善的,在spring中 FileSystemResourceLoader 类继承了 DefaultResourceLoader ,同时重写了 getResourceByPath() 方法,使用标准的文件系统读入,并且返回 FileSystemContextResource 类型

  1. public class FileSystemResourceLoader extends DefaultResourceLoader {
  2. /**
  3. * Resolve resource paths as file system paths.
  4. * <p>Note: Even if a given path starts with a slash, it will get
  5. * interpreted as relative to the current VM working directory.
  6. * @param path the path to the resource
  7. * @return the corresponding Resource handle
  8. * @see FileSystemResource
  9. * @see org.springframework.web.context.support.ServletContextResourceLoader#getResourceByPath
  10. */
  11. @Override
  12. protected Resource getResourceByPath(String path) {
  13. if (path.startsWith("/")) {
  14. path = path.substring(1);
  15. }
  16. return new FileSystemContextResource(path);
  17. }
  18. /**
  19. * FileSystemResource that explicitly expresses a context-relative path
  20. * through implementing the ContextResource interface.
  21. */
  22. private static class FileSystemContextResource extends FileSystemResource implements ContextResource {
  23. public FileSystemContextResource(String path) {
  24. super(path);
  25. }
  26. @Override
  27. public String getPathWithinContext() {
  28. return getPath();
  29. }
  30. }
  31. }
  • 我们可以从 上面的代码中看到 在 FileSystemResourceLoader 中有一个私有的内部类 FileSystemContextResource , 这个类继承了 FileSystemResource ,同时实现了 ContextResource 接口

  • FileSystemContextResource 通过构造函数调用 FileSystemResource 的构造函数,创建 FileSystemResource 类型资源定义,同时实现 ContextResource 是为了实现其中的 getPathWithinContext() 方法,这个方法是用来获取上下文根路径的, 源码中这样写的 :

/**

  1. * Return the path within the enclosing 'context'.
  2. * This is typically path relative to a context-specific root directory,
  3. * e.g. a ServletContext root or a PortletContext root.
  4. */

3.3 ClassRelativeResourceLoader

org.springframework.core.io.ClassRelativeResourceLoader 类也是 DefaultResourceLoader 的另一个实现子类,与 FileSystemResourceLoader 类似,也同样重写了 getResourceByPath() 方法,也内部维护了一个私有的内部类 ClassRelativeContextResource , 具体代码如下:

  1. /**
  2. * 从给定的 class 下加载资源
  3. * {@link ResourceLoader} implementation that interprets plain resource paths
  4. * as relative to a given {@code java.lang.Class}.
  5. *
  6. * @author Juergen Hoeller
  7. * @since 3.0
  8. * @see Class#getResource(String)
  9. * @see ClassPathResource#ClassPathResource(String, Class)
  10. */
  11. public class ClassRelativeResourceLoader extends DefaultResourceLoader {
  12. private final Class<?> clazz;
  13. /**
  14. * Create a new ClassRelativeResourceLoader for the given class.
  15. * @param clazz the class to load resources through
  16. */
  17. public ClassRelativeResourceLoader(Class<?> clazz) {
  18. Assert.notNull(clazz, "Class must not be null");
  19. this.clazz = clazz;
  20. setClassLoader(clazz.getClassLoader());
  21. }
  22. /**
  23. * 重写getResourceByPath 方法 , 返回一个ClassRelativeContextResource 资源类型
  24. * @param path the path to the resource
  25. * @return
  26. */
  27. @Override
  28. protected Resource getResourceByPath(String path) {
  29. return new ClassRelativeContextResource(path, this.clazz);
  30. }
  31. /**
  32. * 继承 ClassPathResource 定义资源类型,实现ContextResource 中的 getPathWithinContext 方法,
  33. *
  34. * ClassPathResource that explicitly expresses a context-relative path
  35. * through implementing the ContextResource interface.
  36. */
  37. private static class ClassRelativeContextResource extends ClassPathResource implements ContextResource {
  38. private final Class<?> clazz;
  39. /**
  40. * 调用父类 ClassPathResource 对资源进行初始化
  41. * @param path
  42. * @param clazz
  43. */
  44. public ClassRelativeContextResource(String path, Class<?> clazz) {
  45. super(path, clazz);
  46. this.clazz = clazz;
  47. }
  48. @Override
  49. public String getPathWithinContext() {
  50. return getPath();
  51. }
  52. /**
  53. * 重写 ClassPathContext 中方法, 通过给定的路径返回一个ClassRelativeContextResource资源
  54. * @param relativePath the relative path (relative to this resource)
  55. * @return
  56. */
  57. @Override
  58. public Resource createRelative(String relativePath) {
  59. String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
  60. return new ClassRelativeContextResource(pathToUse, this.clazz);
  61. }
  62. }
  63. }

3.4 ResourcePatternResolver

org.springframework.core.io.support.ResourcePatternResolver 是对 ResourceLoader 的一个扩展,我们在 ResourceLoader 中通过 getResource 方法获取 Resource 实例时,只能通过一个 location 来获取一个 Resource , 而不能获取到多个 Resource , 当我们需要加载多个资源时,只能通过调用多次的该方法来实现,所以spring 提供了 ResourcePatternResolver 对其进行了扩展,实现了通过 location 来加载多个资源,类的定义如下:

  1. public interface ResourcePatternResolver extends ResourceLoader {
  2. /**
  3. * Pseudo URL prefix for all matching resources from the class path: "classpath*:"
  4. * This differs from ResourceLoader's classpath URL prefix in that it
  5. * retrieves all matching resources for a given name (e.g. "/beans.xml"),
  6. * for example in the root of all deployed JAR files.
  7. * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
  8. */
  9. String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
  10. /**
  11. * Resolve the given location pattern into Resource objects.
  12. * <p>Overlapping resource entries that point to the same physical
  13. * resource should be avoided, as far as possible. The result should
  14. * have set semantics.
  15. * @param locationPattern the location pattern to resolve
  16. * @return the corresponding Resource objects
  17. * @throws IOException in case of I/O errors
  18. */
  19. Resource[] getResources(String locationPattern) throws IOException;
  20. }
  • 可以看到 ResourcePatternResolver 新增加了一个方法 getResources ,返回一个 Resource 数组

  • 这里我们要注意, ResourcePatternResolver 增加了一个新的协议前缀 classpath*: , 看到这里是不是大家可以很熟悉的想起我们在平时配置路径时经常会写 classpath:classpath*: ,那么他们的区别就在这里,他们的资源加载方式时不一样的

3.5 PathMatchingResourcePatternResolver

org.springframework.core.io.support.PathMatchingResourcePatternResolverResourcePatternResolver 的一个主要实现类,也是使用较多的一个实现类,我们可以来看一下,它主要实现了 新增前缀的解析,同时还支持 Ant 风格的路径匹配模式(如 : "**/*.xml" )

3.5.1 构造函数

PathMatchingResourcePatternResolver 提供了三个构造函数:

  1. /**
  2. * 内置 资源定位加载器
  3. */
  4. private final ResourceLoader resourceLoader;
  5. /**
  6. * Ant路径匹配器
  7. */
  8. private PathMatcher pathMatcher = new AntPathMatcher();
  9. /**
  10. * 无参构造函数,当不指定内部加载器类型时,默认是 DefaultResourceLoader
  11. * Create a new PathMatchingResourcePatternResolver with a DefaultResourceLoader.
  12. * <p>ClassLoader access will happen via the thread context class loader.
  13. * @see org.springframework.core.io.DefaultResourceLoader
  14. */
  15. public PathMatchingResourcePatternResolver() {
  16. this.resourceLoader = new DefaultResourceLoader();
  17. }
  18. /**
  19. * 指定特定的资源定位加载器
  20. * Create a new PathMatchingResourcePatternResolver.
  21. * <p>ClassLoader access will happen via the thread context class loader.
  22. * @param resourceLoader the ResourceLoader to load root directories and
  23. * actual resources with
  24. */
  25. public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
  26. Assert.notNull(resourceLoader, "ResourceLoader must not be null");
  27. this.resourceLoader = resourceLoader;
  28. }
  29. /**
  30. * 使用默认的资源加载器,但是传入 classLoader ,使用特定的类加载
  31. * Create a new PathMatchingResourcePatternResolver with a DefaultResourceLoader.
  32. * @param classLoader the ClassLoader to load classpath resources with,
  33. * or {@code null} for using the thread context class loader
  34. * at the time of actual resource access
  35. * @see org.springframework.core.io.DefaultResourceLoader
  36. */
  37. public PathMatchingResourcePatternResolver(@Nullable ClassLoader classLoader) {
  38. this.resourceLoader = new DefaultResourceLoader(classLoader);
  39. }
  • 我们可以看到,当构造函数不提供 ResourceLoader 时,默认是 DefaultResourceLoader

3.5.2 getResource

PathMatchingResourcePatternResolver 中的 getResource 方法的实现是调用了 传入的 ResourceLoader 或者默认的 DefaultResourceLoader , 具体的代码实现如下:

  1. /**
  2. * 调用getResourceLoader 获取当前的 ResourceLoader
  3. * @param location the resource location
  4. * @return
  5. */
  6. @Override
  7. public Resource getResource(String location) {
  8. return getResourceLoader().getResource(location);
  9. }
  10. /**
  11. * Return the ResourceLoader that this pattern resolver works with.
  12. */
  13. public ResourceLoader getResourceLoader() {
  14. return this.resourceLoader;
  15. }

3.5.3 getResources

实现了 ResourcePatternResolvergetResources 方法,可以通过 location 加载多个资源,进行分类处理,如果是没有 classpath*: 前缀以及不包含通配符的情况下直接调用当前类的 ResourceLoader 来进行处理,其他按具体来处理,主要涉及两个方法 #findPathMatchingResources(...)#findAllClassPathResources(...)

  1. @Override
  2. public Resource[] getResources(String locationPattern) throws IOException {
  3. Assert.notNull(locationPattern, "Location pattern must not be null");
  4. //1. 判断 是不是classpath* 开头的
  5. if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
  6. //1.1.进行路径匹配校验 是否包含通配符
  7. // a class path resource (multiple resources for same name possible)
  8. if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
  9. // a class path resource pattern
  10. return findPathMatchingResources(locationPattern);
  11. }
  12. else {
  13. //1.2 不包含通配符
  14. // all class path resources with the given name
  15. return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
  16. }
  17. }
  18. else {
  19. // 2. 不是classpath前缀开头
  20. // Generally only look for a pattern after a prefix here,
  21. // and on Tomcat only after the "*/" separator for its "war:" protocol.
  22. int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
  23. locationPattern.indexOf(':') + 1);
  24. //2.1 校验是否包含通配符
  25. if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
  26. // a file pattern
  27. return findPathMatchingResources(locationPattern);
  28. }
  29. else {
  30. //2.2 不包含通配符 使用内部 ResourceLoader 进行资源加载 默认是 DefaultReourceLoader
  31. // a single resource with the given name
  32. return new Resource[] {getResourceLoader().getResource(locationPattern)};
  33. }
  34. }
  35. }

3.5.4 findPathMatchingResources

上面代码中我们可以看到,当存在通配符时都会执行 #findPathMatchingResources(...) 方法,我们来看一下方法的定义:

  1. /**
  2. * 通过ant解析器来对给定的路径下的所有模糊资源进行解析和匹配
  3. * 支持jar和zip以及系统中的文件资源
  4. * Find all resources that match the given location pattern via the
  5. * Ant-style PathMatcher. Supports resources in jar files and zip files
  6. * and in the file system.
  7. * @param locationPattern the location pattern to match
  8. * @return the result as Resource array
  9. * @throws IOException in case of I/O errors
  10. * @see #doFindPathMatchingJarResources
  11. * @see #doFindPathMatchingFileResources
  12. * @see org.springframework.util.PathMatcher
  13. */
  14. protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
  15. //解析根路径
  16. String rootDirPath = determineRootDir(locationPattern);
  17. //解析到子路径
  18. String subPattern = locationPattern.substring(rootDirPath.length());
  19. //获取根路径的资源
  20. Resource[] rootDirResources = getResources(rootDirPath);
  21. Set<Resource> result = new LinkedHashSet<>(16);
  22. //遍历
  23. for (Resource rootDirResource : rootDirResources) {
  24. rootDirResource = resolveRootDirResource(rootDirResource);
  25. URL rootDirUrl = rootDirResource.getURL();
  26. //判断资源是不是 bundle 类型
  27. if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
  28. URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
  29. if (resolvedUrl != null) {
  30. rootDirUrl = resolvedUrl;
  31. }
  32. rootDirResource = new UrlResource(rootDirUrl);
  33. }
  34. //判断资源是否是 vfs 类型的
  35. if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
  36. result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
  37. }
  38. //判断是否是 jar 形式的
  39. else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
  40. result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
  41. }
  42. //如果都不是
  43. else {
  44. result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
  45. }
  46. }
  47. if (logger.isTraceEnabled()) {
  48. logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
  49. }
  50. //转换为数组返回
  51. return result.toArray(new Resource[0]);
  52. }
  • spring 中很多真真做操作的方法命名都是以 do 开头,我们从上面可以看到核心方法 #doFindPathMatchingFileResources(...)#doFindPathMatchingJarResources(...) 这两个基本一样知识解析不懂的文件类型,另外还有一个方法 #determineRootDir(...) 方法实现了路径的解析,下面我们简单看一这两个实现。
3.5.4.1 determineRootDir(... )

determineRootDir 方法主要用于根路径的获取,解析路径中的通配符,代码如下:

  1. /**
  2. * 通过给定的路径来获取根目录路径
  3. * Determine the root directory for the given location.
  4. * <p>Used for determining the starting point for file matching,
  5. * resolving the root directory location to a {@code java.io.File}
  6. * and passing it into {@code retrieveMatchingFiles}, with the
  7. * remainder of the location as pattern.
  8. * <p>Will return "/WEB-INF/" for the pattern "/WEB-INF/*.xml",
  9. * for example.
  10. * @param location the location to check
  11. * @return the part of the location that denotes the root directory
  12. * @see #retrieveMatchingFiles
  13. */
  14. protected String determineRootDir(String location) {
  15. //1. 找到最后 路径中出现的 : 的索引 +1 ,这里注意我们的路径时 类似 : classpath*: /web-inf/*.xml
  16. int prefixEnd = location.indexOf(':') + 1;
  17. //2. 获取跟路径长度
  18. int rootDirEnd = location.length();
  19. //3.判断冒号后面的路径是否包含通配符 如果包含,则截断最后一个由”/”分割的部分。
  20. while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
  21. rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
  22. }
  23. //
  24. if (rootDirEnd == 0) {
  25. rootDirEnd = prefixEnd;
  26. }
  27. return location.substring(0, rootDirEnd);
  28. }

举例看一下:

原路径 获取跟路径
classpath*:/test/aa*/app-*.xml classpath*:/test/
classpath*:/test/aa/app-*.xml classpath*:/test/aa
3.5.4.2 doFindPathMatchingFileResources(... )

#doFindPathMatchingFileResources(...)#doFindPathMatchingJarResources(...) 方法的的内部基本一致,只是解析不同的类型文件,我们这里只看其中一个则可,大家可以自行比对两者的区别。

  • 我们跟一下 #doFindPathMatchingFileResources(...) 方法,方法内部调用较深,所以下面我主要把代码贴出来,注释已有,相信可以看的懂

  • #doFindPathMatchingFileResources(...) 代码:

  1. /**
  2. * 查找文件系统符合给定的location的资源, 路径符合 ant 样式的通配符
  3. * Find all resources in the file system that match the given location pattern
  4. * via the Ant-style PathMatcher.
  5. * @param rootDirResource the root directory as Resource
  6. * @param subPattern the sub pattern to match (below the root directory)
  7. * @return a mutable Set of matching Resource instances
  8. * @throws IOException in case of I/O errors
  9. * @see #retrieveMatchingFiles
  10. * @see org.springframework.util.PathMatcher
  11. */
  12. protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
  13. throws IOException {
  14. File rootDir;
  15. try {
  16. //获取绝对路径对应的文件目录
  17. rootDir = rootDirResource.getFile().getAbsoluteFile();
  18. }
  19. catch (FileNotFoundException ex) {
  20. if (logger.isDebugEnabled()) {
  21. logger.debug("Cannot search for matching files underneath " + rootDirResource +
  22. " in the file system: " + ex.getMessage());
  23. }
  24. return Collections.emptySet();
  25. }
  26. catch (Exception ex) {
  27. if (logger.isInfoEnabled()) {
  28. logger.info("Failed to resolve " + rootDirResource + " in the file system: " + ex);
  29. }
  30. return Collections.emptySet();
  31. }
  32. //调用真真处理方法
  33. return doFindMatchingFileSystemResources(rootDir, subPattern);
  34. }
  • 上面方法中主要调用了核心流程 #doFindMatchingFileSystemResources(...) , 代码如下:
  1. /**
  2. * 通过ant通配符的subPattern与已经获取的根目录rootDir来组合获取所有在文件系统中的资源
  3. * 如:我们本来的 url: 'classpath*:/test/aa/app-*.xml'
  4. * 那么这里rootDir:classpath*:/test/aa/ subPattern :app-*.xml
  5. * Find all resources in the file system that match the given location pattern
  6. * via the Ant-style PathMatcher.
  7. * @param rootDir the root directory in the file system
  8. * @param subPattern the sub pattern to match (below the root directory)
  9. * @return a mutable Set of matching Resource instances
  10. * @throws IOException in case of I/O errors
  11. * @see #retrieveMatchingFiles
  12. * @see org.springframework.util.PathMatcher
  13. */
  14. protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
  15. if (logger.isTraceEnabled()) {
  16. logger.trace("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
  17. }
  18. //调用真实处理方法,获取set集合的File
  19. Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
  20. Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
  21. //将获取的File转换为 FileSystemResource 同时添加到result结果集中
  22. for (File file : matchingFiles) {
  23. result.add(new FileSystemResource(file));
  24. }
  25. return result;
  26. }

上面方法主要是将获取的 Set<File> 的结果进行转换,将资源类型转换为 FileSystemResource , 上面方法的核心方法是 #retrieveMatchingFiles(...)

  • #retrieveMatchingFiles(...) 代码如下:
  1. /**
  2. *
  3. * Retrieve files that match the given path pattern,
  4. * checking the given directory and its subdirectories.
  5. * @param rootDir the directory to start from
  6. * @param pattern the pattern to match against,
  7. * relative to the root directory
  8. * @return a mutable Set of matching Resource instances
  9. * @throws IOException if directory contents could not be retrieved
  10. */
  11. protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
  12. //1.不存在直接返回空集合
  13. if (!rootDir.exists()) {
  14. // Silently skip non-existing directories.
  15. if (logger.isDebugEnabled()) {
  16. logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
  17. }
  18. return Collections.emptySet();
  19. }
  20. //2.不是目录直接返回空
  21. if (!rootDir.isDirectory()) {
  22. // Complain louder if it exists but is no directory.
  23. if (logger.isInfoEnabled()) {
  24. logger.info("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
  25. }
  26. return Collections.emptySet();
  27. }
  28. //3/判断是否可读
  29. if (!rootDir.canRead()) {
  30. if (logger.isInfoEnabled()) {
  31. logger.info("Skipping search for matching files underneath directory [" + rootDir.getAbsolutePath() +
  32. "] because the application is not allowed to read the directory");
  33. }
  34. return Collections.emptySet();
  35. }
  36. //4.将所有的系统分割器转换为 /
  37. String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
  38. //5.若子路径开头没有 / 则父路径要最后添加 /
  39. if (!pattern.startsWith("/")) {
  40. fullPattern += "/";
  41. }
  42. fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
  43. Set<File> result = new LinkedHashSet<>(8);
  44. //真真处理的方法
  45. doRetrieveMatchingFiles(fullPattern, rootDir, result);
  46. return result;
  47. }

我们可以看到方法中主要是做了一些校验和转换,真真的处理是调用了 #doRetrieveMatchingFiles(...) 方法,

  • #doRetrieveMatchingFiles(...) 方法定义:
  1. /**
  2. * 递归遍历 dir 目录 结合fullpattern 进行路径匹配,将符合的资源全部放入result中
  3. * Recursively retrieve files that match the given pattern,
  4. * adding them to the given result list.
  5. * @param fullPattern the pattern to match against,
  6. * with prepended root directory path
  7. * @param dir the current directory
  8. * @param result the Set of matching File instances to add to
  9. * @throws IOException if directory contents could not be retrieved
  10. */
  11. protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
  12. if (logger.isTraceEnabled()) {
  13. logger.trace("Searching directory [" + dir.getAbsolutePath() +
  14. "] for files matching pattern [" + fullPattern + "]");
  15. }
  16. //遍历目录
  17. for (File content : listDirectory(dir)) {
  18. //获取当前文件/目录的路径同时分隔符全部替换为 /
  19. String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
  20. //如果是目录 同时和 fullPattern匹配 则进递归
  21. if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
  22. if (!content.canRead()) {
  23. if (logger.isDebugEnabled()) {
  24. logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
  25. "] because the application is not allowed to read the directory");
  26. }
  27. }
  28. else {
  29. doRetrieveMatchingFiles(fullPattern, content, result);
  30. }
  31. }
  32. //如果是文件则进行匹配
  33. if (getPathMatcher().match(fullPattern, currPath)) {
  34. result.add(content);
  35. }
  36. }
  37. }

3.5.5 findAllClassPathResources

上面分析了当有通配符时的方法调用过程,那么这里我们来分析当没有通配符时的方法调用

  • #findAllClassPathResources(...) 方法代码:
  1. /**
  2. * 通过ClassLoader 来 加载所有的 class location
  3. * Find all class location resources with the given location via the ClassLoader.
  4. * Delegates to {@link #doFindAllClassPathResources(String)}.
  5. * @param location the absolute path within the classpath
  6. * @return the result as Resource array
  7. * @throws IOException in case of I/O errors
  8. * @see java.lang.ClassLoader#getResources
  9. * @see #convertClassLoaderURL
  10. */
  11. protected Resource[] findAllClassPathResources(String location) throws IOException {
  12. String path = location;
  13. if (path.startsWith("/")) {
  14. path = path.substring(1);
  15. }
  16. //真实处理方法, 得到资源结果集
  17. Set<Resource> result = doFindAllClassPathResources(path);
  18. if (logger.isTraceEnabled()) {
  19. logger.trace("Resolved classpath location [" + location + "] to resources " + result);
  20. }
  21. return result.toArray(new Resource[0]);
  22. }
  • #doFindAllClassPathResources(...) 方法代码:
  1. /**
  2. * Find all class location resources with the given path via the ClassLoader.
  3. * Called by {@link #findAllClassPathResources(String)}.
  4. * @param path the absolute path within the classpath (never a leading slash)
  5. * @return a mutable Set of matching Resource instances
  6. * @since 4.1.1
  7. */
  8. protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
  9. Set<Resource> result = new LinkedHashSet<>(16);
  10. ClassLoader cl = getClassLoader();
  11. //1.通过ClassLoader获取所有的URl
  12. Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
  13. while (resourceUrls.hasMoreElements()) {
  14. //将URl转换为 UrlResource
  15. URL url = resourceUrls.nextElement();
  16. result.add(convertClassLoaderURL(url));
  17. }
  18. if ("".equals(path)) {
  19. // The above result is likely to be incomplete, i.e. only containing file system references.
  20. // We need to have pointers to each of the jar files on the classpath as well...
  21. //添加所有的jar包
  22. addAllClassLoaderJarRoots(cl, result);
  23. }
  24. return result;
  25. }

方法内相对简单,主要是通过ClassLoader来加载目录下的jar资源,详细不再贴出来,可以自行查看

本文由AnonyStar 发布,可转载但需声明原文出处。

仰慕「优雅编码的艺术」 坚信熟能生巧,努力改变人生

欢迎关注微信公账号 :coder简码 获取更多优质文章

更多文章关注笔者博客 :IT简码

Spring中资源的加载原来是这么一回事啊!的更多相关文章

  1. Spring中资源的加载ResourceLoader

    http://blog.csdn.net/u011955252/article/details/52912571

  2. SpringBoot中资源初始化加载的几种方式(看这一片就够了)

    一.问题 在平时的业务模块开发过程中,难免会需要做一些全局的任务.缓存.线程等等的初始化工作,那么如何解决这个问题呢?方法有多种,但具体又要怎么选择呢? 二.资源初始化 1.既然要做资源的初始化,那么 ...

  3. SpringBoot中资源初始化加载的几种方式

    一.问题 在平时的业务模块开发过程中,难免会需要做一些全局的任务.缓存.线程等等的初始化工作,那么如何解决这个问题呢?方法有多种,但具体又要怎么选择呢? 二.资源初始化 1.既然要做资源的初始化,那么 ...

  4. 【Spring】详解Spring中Bean的加载

    之前写过bean的解析,这篇来讲讲bean的加载,加载要比bean的解析复杂些,该文之前在小编原文中有发表过,要看原文的可以直接点击原文查看,从之前的例子开始,Spring中加载一个bean的方式: ...

  5. Unity中资源动态加载的几种方式比较

    http://blog.csdn.net/leonwei/article/details/18406103 初学Unity的过程中,会发现打包发布程序后,unity会自动将场景需要引用到的资源打包到安 ...

  6. 深入Spring之IOC之加载BeanDefinition

    本文主要分析 spring 中 BeanDefinition 的加载,对于其解析我们在后面的文章中专门分析. BeanDefinition 是属于 Spring Bean 模块的,它是对 spring ...

  7. Spring Boot JPA 懒加载

    最近在使用spring jpa 的过程中经常遇到懒加载的错误:"` org.hibernate.LazyInitializationException: could not initiali ...

  8. Spring boot 国际化自动加载资源文件问题

    Spring boot 国际化自动加载资源文件问题 最近在做基于Spring boot配置的项目.中间遇到一个国际化资源加载的问题,正常来说只要在application.properties文件中定义 ...

  9. 在Unity3D的网络游戏中实现资源动态加载

    用Unity3D制作基于web的网络游戏,不可避免的会用到一个技术-资源动态加载.比如想加载一个大场景的资源,不应该在游戏的开始让用户长时间等待全部资源的加载完毕.应该优先加载用户附近的场景资源,在游 ...

随机推荐

  1. python3(六) for while

    # Python的循环有两种,一种是for...in循环,依次把list或tuple中的每个元素迭代出来 names = ['Michael', 'Bob', 'Tracy'] for name in ...

  2. Linux C++ 网络编程学习系列(4)——多路IO之epoll基础

    epoll实现多路IO 源码地址:https://github.com/whuwzp/linuxc/tree/master/epoll 源码说明: server.cpp: 监听127.1:6666,功 ...

  3. 路径跟踪 PathMeasure的简单使用

    平时用path画一些简单的几何图形,呈现的时候也是已经绘制好的图形,想想,如果像动画一样看到它的绘制轨迹,是不是更酷?今天介绍的这个类PathMeasure就是干这个的,知道它的存在还是由于看了启舰写 ...

  4. 《JavaScript 模式》读书笔记(6)— 代码复用模式2

    上一篇讲了最简单的代码复用模式,也是最基础的,我们普遍知道的继承模式,但是这种继承模式却有不少缺点,我们下面再看看其它可以实现继承的模式. 四.类式继承模式#2——借用构造函数 本模式解决了从子构造函 ...

  5. MySQL的事务隔离级别是什么?

    我是平也,这有一个专注Gopher技术成长的开源项目「go home」 背景介绍 想必事务大家都已经非常熟悉了,它是一组SQL组成的一个执行单元,要么全执行要么全不执行,这也是它的一个特性--原子性. ...

  6. api_DZFPKJ & api_DZFPCX

    AES加密算法的网站:http://www.ssleye.com/aes_cipher.html """ AES加密(加解密算法/工作模式/填充方式:AES/ECB/PK ...

  7. 从hfctf学习JWT伪造

    本文作者:Ch3ng easy_login 简单介绍一下什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519) ...

  8. JavaScript_Array

    Array 概念特点 值的有序集合: 每一个值叫一个元素: 每个元素在数组中有一个位置,以数字表示,称为索引(下标): 元素可以是任何类型 索引从0开始,最大为2的32次方 数组的创建 数组直接量 v ...

  9. [HarekazeCTF2019] web

    在 buuoj 上看到的这个比赛题目,期间平台关了,就拿了 Dockerfile 本地做了,web 题目感觉还不错 encode_and_encode [100] 打开靶机,前两个页面都是 html ...

  10. WPF中在Gmap.net中将Marker动起来

    前一段时间说过一篇绘制极坐标的,这段时间对它进行了改造已经今非昔比了,功能实现了很多,我目的是让Marker动起来,然后还会绘制Route,上篇也就是简单的绘制了Route,没有关于Marker的相关 ...