Loading

Spring的Resource体系介绍

1 Resource

org.springframework.core.io.Resource是Spring对底层资源的统一抽象。它提供了访问资源的统一方法:

  • 获取资源信息。
  • 获取输入流。
  • 获取对应FileURI对象。

Resource在Spring IoC容器中有着广泛应用,主要作用是读取不同形式的依赖配置。例如,ClassPathBeanDefinitionScanner会扫描指定路径的所有bean对象,将路径封装成UrlResourceFileUrlResource

Spring提供了很多内置实现类,这里简单介绍一些典型的。

1.1 UrlResource

UrlResource封装了java.net.URL对象,提供了对url资源的统一处理。

通过指定标准的url地址前缀,就可以很简单地创建UrlResource对象:

  • file::文件系统路径。
  • https::通过HTTPS协议获取资源。
  • ftp::通过FTP协议获取资源。
  • ……

1.2 ClassPathResource

ClassPathResource提供了对类路径下资源的统一处理。

如果是在文件系统类路径下的资源(非JAR包中),会解析成java.io.File对象,否则会解析成java.net.URL对象。

1.3 FileSystemResource

FileSystemResource提供了对文件系统中资源的统一处理。

1.4 PathResource

PathResource提供了对文件系统中资源的统一处理,它会将资源解析成java.nio.file.Path对象进行处理。

1.5 ServletContextResource

ServletContextResource提供了对Web应用根路径下资源的统一处理。

1.6 ByteArrayResource

ByteArrayResource缓存了byteArray数据,可以重复获取。

1.7 InputStreamResource

InputStreamResource是对InputStream的封装,它应该作为Resource的兜底选择。

2 ResourceLoader

ResourceLoader是加载类路径或文件系统资源的工具类,它可以根据指定的路径获取对应的Resource

ResourceLoader的基础实现类是DefaultResourceLoader,其中定义了加载资源的基本逻辑,默认会解析成ClassPathResourceClassPathContextResource对象。

ServletContextResourceLoaderFileSystemResourceLoaderClassRelativeResourceLoader子类会重写getResourceByPath()方法,分别解析成对应的XxxResource对象。

需要注意的是,AbstractApplicationContext也继承了DefaultResourceLoader类,它会采取相同的逻辑加载资源。

但是,AbstractApplicationContext的子类GenericApplicationContext重写了这一规则,它内部持有ResourceLoader对象,可以动态进行资源解析,只有在未设置的情况下才会使用默认规则。

2.1 DefaultResourceLoader

DefaultResourceLoaderResourceLoader的默认实现类,它的getResource()会依次使用以下方式进行解析:

  1. 使用protocolResolvers成员变量,根据协议进行解析。
  2. 如果以/开头,调用getResourceByPath()方法解析,默认返回ClassPathContextResource对象。
  3. 如果以classpath:开头,返回ClassPathResource对象。
  4. 如果是文件协议,返回FileUrlResource对象。
  5. 如果不是文件协议,返回UrlResource对象
  6. 如果以上都不能解析,再次调用getResourceByPath()方法解析,默认返回ClassPathContextResource对象。

DefaultResourceLoader#getResource()方法源码如下:

public Resource getResource(String location) {  
   Assert.notNull(location, "Location must not be null");  
   // 使用`protocolResolvers`成员变量,根据协议进行解析
   for (ProtocolResolver protocolResolver : getProtocolResolvers()) {  
      Resource resource = protocolResolver.resolve(location, this);  
      if (resource != null) {  
         return resource;  
      }  
   }  
   // 如果以`/`开头,调用getResourceByPath()方法解析,默认返回ClassPathContextResource对象。
   if (location.startsWith("/")) {  
      return getResourceByPath(location);  
   }  
   else if (location.startsWith(CLASSPATH_URL_PREFIX)) {  
      return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());  
   }  
   else {  
      try {  
         // Try to parse the location as a URL...  
         URL url = new URL(location);  
         return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));  
      }  
      catch (MalformedURLException ex) {  
         // No URL -> resolve as resource path.  
         return getResourceByPath(location);  
      }  
   }  
}

DefaultResourceLoader#getResourceByPath()方法默认返回ClassPathContextResource对象:

protected Resource getResourceByPath(String path) {  
   return new ClassPathContextResource(path, getClassLoader());  
}

2.2 ServletContextResourceLoader

ServletContextResourceLoader#getResourceByPath()方法会返回ServletContextResource对象:

protected Resource getResourceByPath(String path) {  
   return new ServletContextResource(this.servletContext, path);  
}

2.3 FileSystemResourceLoader

FileSystemResourceLoader#getResourceByPath()方法会返回FileSystemContextResource对象:

protected Resource getResourceByPath(String path) {  
   if (path.startsWith("/")) {  
      path = path.substring(1);  
   }  
   return new FileSystemContextResource(path);  
}

2.4 ClassRelativeResourceLoader

ClassRelativeResourceLoader#getResourceByPath()方法会返回ClassRelativeContextResource对象:

protected Resource getResourceByPath(String path) {  
   return new ClassRelativeContextResource(path, this.clazz);  
}

2.5 AbstractApplicationContext

AbstractApplicationContext继承了DefaultResourceLoader,但是它没有重写getResourceByPath()方法,因此还是会返回ClassPathContextResource对象。

但是,AbstractApplicationContext的子类GenericApplicationContext重写了getResourceByPath()方法,它内部持有ResourceLoader对象,可以动态进行资源解析,只有在未设置的情况下才会使用默认规则:

public Resource getResource(String location) {  
   if (this.resourceLoader != null) {  
      for (ProtocolResolver protocolResolver : getProtocolResolvers()) {  
         Resource resource = protocolResolver.resolve(location, this);  
         if (resource != null) {  
            return resource;  
         }  
      }  
      return this.resourceLoader.getResource(location);  
   }  
   return super.getResource(location);  
}

3 ResourcePatternResolver

ResourcePatternResolver继承了ResourceLoader接口,并新增了getResources()方法,可以根据通配符等路径模式获取资源。

ResourcePatternResolver的基础实现类是PathMatchingResourcePatternResolver,其中定义了加载资源的基本逻辑。

需要注意的是,Application继承了ResourcePatternResolver接口,因此它本身就具备加载资源的功能。

3.1 PathMatchingResourcePatternResolver

PathMatchingResourcePatternResolver#getResources()方法定义了加载资源的基本逻辑:

public Resource[] getResources(String locationPattern) throws IOException {  
   Assert.notNull(locationPattern, "Location pattern must not be null");  
   // 以"classpath*:"开头
   if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {  
      // 如果路径满足pathMatcher规则,按规则加载资源
      if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {  
         return findPathMatchingResources(locationPattern);  
      }  
      // 如果路径不满足pathMatcher规则,查找所有类路径资源
      else {  
         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.      
      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)};  
      }  
   }  
}

PathMatchingResourcePatternResolver#findPathMatchingResources()方法会查找符合规则的资源(JAR文件、ZIP文件或文件系统中):

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {  
   // 解析根路径:例如通配符前的地址(“/WEB-INF/*.xml” → “/WEB-INF/”)
   String rootDirPath = determineRootDir(locationPattern);  
   // 解析子路径:“/WEB-INF/*.xml” → “*.xml”
   String subPattern = locationPattern.substring(rootDirPath.length());  
   // 递归获取根路径的资源:默认调用DefaultResourceLoader获取资源
   Resource[] rootDirResources = getResources(rootDirPath);  
   Set<Resource> result = new LinkedHashSet<>(16);  
   // 遍历根路径,继续查找符合规则的资源
   for (Resource rootDirResource : rootDirResources) {  
      rootDirResource = resolveRootDirResource(rootDirResource);  
      URL rootDirUrl = rootDirResource.getURL();  
      if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {  
         URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);  
         if (resolvedUrl != null) {  
            rootDirUrl = resolvedUrl;  
         }  
         rootDirResource = new UrlResource(rootDirUrl);  
      }  
      if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {  
         result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));  
      }  
      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);  
   }  
   return result.toArray(new Resource[0]);  
}

PathMatchingResourcePatternResolver#findAllClassPathResources()方法会查找类路径上的资源:

protected Resource[] findAllClassPathResources(String location) throws IOException {  
   String path = location;  
   if (path.startsWith("/")) {  
      path = path.substring(1);  
   }  
   Set<Resource> result = doFindAllClassPathResources(path);  
   if (logger.isTraceEnabled()) {  
      logger.trace("Resolved classpath location [" + location + "] to resources " + result);  
   }  
   return result.toArray(new Resource[0]);  
}

3.2 AbstractApplicationContext

AbstractApplicationContext有个resourcePatternResolver成员变量,它的getResources()方法会交给这个成员变量执行:

public Resource[] getResources(String locationPattern) throws IOException {  
   return this.resourcePatternResolver.getResources(locationPattern);  
}

GenericApplicationContext会重写这个方法:

public Resource[] getResources(String locationPattern) throws IOException {  
   if (this.resourceLoader instanceof ResourcePatternResolver) {  
      return ((ResourcePatternResolver) this.resourceLoader).getResources(locationPattern);  
   }  
   return super.getResources(locationPattern);  
}

4 ResourceLoader和ApplicationContext的关系

ApplicationContext继承了ResourcePatternResolver接口,实现了getResource()getResources()方法。

5 ResourceLoaderAware

只要某个bean实现了ResourceLoaderAware接口,在ApplicationContext实例化bean对象时,会将自身作为形参,触发setResourceLoader()方法。

具体源码位于ApplicationContextAwareProcessor#invokeAwareInterfaces()方法:

private void invokeAwareInterfaces(Object bean) {  
   if (bean instanceof EnvironmentAware) {  
      ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());  
   }  
   if (bean instanceof EmbeddedValueResolverAware) {  
      ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);  
   }  
   if (bean instanceof ResourceLoaderAware) {  
      ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);  
   }  
   if (bean instanceof ApplicationEventPublisherAware) {  
      ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);  
   }  
   if (bean instanceof MessageSourceAware) {  
      ((MessageSourceAware) bean).setMessageSource(this.applicationContext);  
   }  
   if (bean instanceof ApplicationStartupAware) {  
      ((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup());  
   }  
   if (bean instanceof ApplicationContextAware) {  
      ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);  
   }  
}
posted @ 2023-01-12 23:17  Xianuii  阅读(532)  评论(0编辑  收藏  举报