【小家Spring】资源访问利器---Spring使用ResourceLoader接口便捷的获取资源(ResourcePatternResolver、ResourceLoaderAware)(下)

简介: 【小家Spring】资源访问利器---Spring使用ResourceLoader接口便捷的获取资源(ResourcePatternResolver、ResourceLoaderAware)(下)


PathMatchingResourcePatternResolver


该实现类是本文的重中之重。

它是基于模式匹配的,默认使用org.springframework.util.AntPathMatcher进行路径匹配,它除了支持ResourceLoader支持的前缀外,还额外支持classpath*:用于加载所有匹配的类路径Resource

public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
  // 内部持有一个resourceLoader的引用
  private final ResourceLoader resourceLoader;
  // 我们发现,它内部使用是AntPathMatcher进行匹配的(Spring内部AntPathMatcher是PathMatcher接口的唯一实现。
  //如果你想改变此匹配规则,你可以自己实现(当然这是完全没必要的))
  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);
  }
  ...
  // 显然,我们也可以自己指定解析器,默认使用AntPathMatcher
  public void setPathMatcher(PathMatcher pathMatcher) {
    Assert.notNull(pathMatcher, "PathMatcher must not be null");
    this.pathMatcher = pathMatcher;
  }
  // 最终是委托给ResourceLoader去做了
  @Override
  public Resource getResource(String location) {
    return getResourceLoader().getResource(location);
  }
  // 这个是核心方法~~~~~~~
  @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)) {
      // 把前最后面那部分截取出来,看看是否是模版(包含*和?符号都属于模版,否则不是)
      if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
        // a class path resource pattern
        // 事patter,那就交给这个方法,这个是个核心方法   这里传入的是locationPattern
        return findPathMatchingResources(locationPattern);
      }
      else {
        // all class path resources with the given name
        // 如果不是pattern,那就完全匹配。去找所有的path下的匹配上的就成~~~
        return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
      }
    }
    // 不是以`classpath*:`打头的~~~~
    else {
      // 支持到tomcat的war:打头的方式~~~
      int prefixEnd = (locationPattern.startsWith("war:") ?  locationPattern.indexOf("*/") + 1 :
          locationPattern.indexOf(':') + 1);
      if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
        return findPathMatchingResources(locationPattern);
      }
      // 如果啥都不打头,那就当作一个正常的处理,委托给ResourceLoader直接去处理
      else {
        // a single resource with the given name
        return new Resource[] {getResourceLoader().getResource(locationPattern)};
      }
    }
  }
  // 根据Pattern去匹配资源~~~~
  protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
    // 定位到跟文件夹地址。比如locationPattern=classpath:META-INF/spring.factories  得到classpath*:META-INF/
    // 若是classpath*:META-INF/ABC/*.factories 得到的就是 classpath*:META-INF/ABC/
    // 简单的说,就是截取第一个不是patter的地方的前半部分
    String rootDirPath = determineRootDir(locationPattern);
    // 后半部分  这里比如就是:*.factories
    String subPattern = locationPattern.substring(rootDirPath.length());
    // 这个递归就厉害了,继续调用了getResources("classpath*:META-INF/")方法
    // 相当于把该文件夹匹配的所有的资源(注意:可能会比较多的),最后在和patter匹配即可~~~~
    // 比如此处:只要jar里面有META-INF目录的  都会被匹配进来~~~~~~
    Resource[] rootDirResources = getResources(rootDirPath);
    Set<Resource> result = new LinkedHashSet<>(16);
    for (Resource rootDirResource : rootDirResources) {
      // resolveRootDirResource是留给子类去复写的。但是Spring没有子类复写此方法,默认实现是啥都没做~~~
      rootDirResource = resolveRootDirResource(rootDirResource);
      URL rootDirUrl = rootDirResource.getURL();
      // 这个if就一般不看了  是否为了做兼容~~~
      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协议(JBoss)~~~
      if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
        result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
      }
      // 是否是jar文件或者是jar资源(显然大多数情况下都是此情况~~~)
      else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
        // 把rootDirUrl, subPattern都交给doFindPathMatchingJarResources去处理
        result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
      }
      // 不是Jar文件(那就是本工程里字的META-INF目录~~~)
      // 那就没啥好说的,直接给个subPattern去匹配吧   注意这个方法名是File,上面是jar
      else {
        result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
      }
    }
    // 最终转换为数组返回。 注意此处的result是个set,是有去重的效果的~~~
    return result.toArray(new Resource[0]);
  }
  protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern) {
    ... // 这个方法就源码不细说了,
    //Find all resources in jar files that match the given location pattern
    // 就是去这个jar里面去找所有的资源(默认利用Ant风格匹配~)
    //此处用到了`java.util.jar.JarFile`、`ZipFile`、`java.net.JarURLConnection`等等
    // 路径匹配:getPathMatcher().match(subPattern, relativePath) 使用的此方法去校对
  }
  // Find all resources in the file system that match the given location pattern
  // 简单的说,这个就是在我们自己的项目里找~~~~
  protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
      throws IOException {
    // rootDir:最终是个绝对的路径地址,带盘符的。来代表META-INF这个文件夹~~~
    File rootDir;
    try {
      rootDir = rootDirResource.getFile().getAbsoluteFile();
    } catch (IOException ex) {
      return Collections.emptySet();
    }
    // FileSystem  最终是根据此绝对路径 去文件系统里找
    return doFindMatchingFileSystemResources(rootDir, subPattern);
  }
  // 这个比较简单:就是把该文件夹所有的文件都拿出来dir.listFiles(),然后一个个去匹配呗~~~~
  // 备注:子类`ServletContextResourcePatternResolver`复写了此方法~~~~~
  protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
    if (logger.isDebugEnabled()) {
      logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
    }
    Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
    Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
    for (File file : matchingFiles) {]
      // 最终用FileSystemResource把File包装成一个Resource~
      result.add(new FileSystemResource(file));
    }
    return result;
  }
  ...
}


至此,整个过程核心就是遍历、递归、匹配、路径的处理。说难也难,说容易也就这么些事。可能路径处理方面是比较复杂也是最容易出错的地方


ServletContextResourcePatternResolver


显然它是web下,用于获取资源的。上面已经介绍了web容器下都是使用它去获取资源~~(ServletContextResourceLoader) 它复写了这个方法:

public class ServletContextResourcePatternResolver extends PathMatchingResourcePatternResolver {
  ...
  // 一般会使用这个构造函数构造~~~
  public ServletContextResourcePatternResolver(ServletContext servletContext) {
    super(new ServletContextResourceLoader(servletContext));
  }
  @Override
  protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
      throws IOException {
    // 如果你这个Resource是web的,那就交给它处理
    // doRetrieveMatchingServletContextResources是核心处理方法~~~
    if (rootDirResource instanceof ServletContextResource) {
      ServletContextResource scResource = (ServletContextResource) rootDirResource;
      ServletContext sc = scResource.getServletContext();
      String fullPattern = scResource.getPath() + subPattern;
      Set<Resource> result = new LinkedHashSet<>(8);
      doRetrieveMatchingServletContextResources(sc, fullPattern, scResource.getPath(), result);
      return result;
    }
    else {
      return super.doFindPathMatchingFileResources(rootDirResource, subPattern);
    }
  }
}


ApplicationContext


ApplicationContext这个接口就再熟悉不过了,它代表整个Spring应用的上下文。它也继承自ResourcePatternResolver,赋予了上下文更便捷的处理Resource的能力。

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
    MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
    ...
}


大部分ApplicationContext的实现类已经在上面都说明了,这里只需要简单的说说WebApplicationContext这个分支~~~


WebApplicationContext


这个是web环境下的容器的父接口。org.springframework.web.context.WebApplicationContext

public interface WebApplicationContext extends ApplicationContext {
  String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
  String SCOPE_REQUEST = "request";
  String SCOPE_SESSION = "session";
  String SCOPE_APPLICATION = "application";
  String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
  String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
  String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";
  // 这个接口相当于只新增了这个一个接口~~~
  @Nullable
  ServletContext getServletContext();
}


image.png


ResourceLoaderAware


这是Spring为了方便我们获得Resource而提供的感知接口~~~

Spring会自动调用实现了ResourceLoaderAware接口类方法:serResourceLoader(),将ApplicationContext的ResourceLoader注入进去,之后对它getResource(),就获取了系统的Resource了,可谓非常的贴心~


总结


applicationContext.getResource()会采用和ApplicationContext相同的策略来访问资源:


  • ClassPathXmlApplicationContext 则底层Resource是ClassPathResource实例
  • FileSystemXmlApplicationContext 则底层Resource是FileSystemResource实例
  • XmlWebApplicationContext 则底层Resource是ServletContextResource实例


所以我们一般这么使用就行,对开发者而言,不用关心具体使用的是那种Resource:

Resource resource = applicationContext.getResource("classpath:spring-mvc.xml");
// 或者
// 即使创建的时候使用的是`classpath:`,但是最后getResource()实际上使用的还是FileSystemResource 这点需要注意~~~
applicationContext=new FileSystemXmlApplicationContext("classpath:bean.xml");
applicationContext.getResource("book.xml");


相关文章
|
1月前
|
XML Java 数据格式
探索Spring之利剑:ApplicationContext接口
本文深入介绍了Spring框架中的核心接口ApplicationContext,解释了其作为应用容器的功能,包括事件发布、国际化支持等,并通过基于XML和注解的配置示例展示了如何使用ApplicationContext管理Bean实例。
86 6
|
1月前
|
XML 安全 Java
|
3月前
|
存储 安全 Java
|
3月前
|
自然语言处理 JavaScript Java
Spring 实现 3 种异步流式接口,干掉接口超时烦恼
本文介绍了处理耗时接口的几种异步流式技术,包括 `ResponseBodyEmitter`、`SseEmitter` 和 `StreamingResponseBody`。这些工具可在执行耗时操作时不断向客户端响应处理结果,提升用户体验和系统性能。`ResponseBodyEmitter` 适用于动态生成内容场景,如文件上传进度;`SseEmitter` 用于实时消息推送,如状态更新;`StreamingResponseBody` 则适合大数据量传输,避免内存溢出。文中提供了具体示例和 GitHub 地址,帮助读者更好地理解和应用这些技术。
548 0
|
3月前
|
存储 NoSQL Java
Spring Boot项目中使用Redis实现接口幂等性的方案
通过上述方法,可以有效地在Spring Boot项目中利用Redis实现接口幂等性,既保证了接口操作的安全性,又提高了系统的可靠性。
81 0
|
1天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
51 17
Spring Boot 两种部署到服务器的方式
|
1天前
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
29 17
springboot自动配置原理
|
6天前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
43 11
|
8天前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
154 12