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

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

前言


关于Spring Framework中资源的访问,上篇已经讲解了org.springframework.core.io.Resource接口,它有非常多的实现类,来针对不同的场景去规范统一的资源获取方式。

【小家Spring】资源访问利器—Spring提供的Resource接口以及它的常用子类源码分析


可能有小伙伴会想,既然有了Resource接口,为啥又搞出来一个ResourceLoader呢?

这其实我认为也是Spring优秀的特点的之一,它非常擅长用接口来规范操作,隔离一些操作。


Spring框架为了更方便的获取资源,尽量弱化程序员对各个Resource接口的实现类的感知(那么多实现类要程序员去记忆,其实也是不小的一个工作量),因此定义了另一个ResourceLoader接口。


ResourceLoader接口


先从接口本身下手,看看都提供了什么功能:


// @since 10.03.2004  小细节:Resource接口是@since 28.12.2003   所以这个接口晚了大概4个月的样子
public interface ResourceLoader {
  // 常量:classpath:
  // 其实ResourceLoader接口只提供了classpath前缀的支持。而classpath*的前缀支持是在它的子接口ResourcePatternResolver中
  String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
  // 这个方法可谓是核心方法:返回Resource实例
  // 它的目的很简单:就是希望程序员在使用时,不需要太在意使用的是哪个实例。面向接口操作即可
  // 至于选取哪个实例去操作,交给框架来完成
  Resource getResource(String location);
  // Expose the ClassLoader used by this ResourceLoader.
  // 暴露出ResourceLoader使用的类加载器~~~
  @Nullable
  ClassLoader getClassLoader();
}


从上可以看出:程序员在使用Spring容器时,可以不去过于计较底层Resource的实现,也不需要自己创建Resource实现类,而是直接使用applicationContext.getResource(),获取到bean容器本身的Resource,进而取到相关的资源信息。


所有ApplicationContext实例都实现了这个接口方法


image.png


从继承体系中可议看出,实现还是非常多的。但是本文先从另外一个角度出发,看看实现了Resource getResource(String location);方法的实现类:


image.png


很清晰的可以看到,真正实现了这个方法的只有三个类而已。而且GenericApplicationContext还继承自DefaultResourceLoader,而PathMatchingResourcePatternResolver实现的是子接口ResourcePatternResolver,因此放到最后说明。


DefaultResourceLoader 默认实现


它是一个非常重要的类,实现了ResourceLoader的所有功能。而且很多的类都会继承于它,再它的基础上进行扩展。

// @since 10.03.2004
public class DefaultResourceLoader implements ResourceLoader {
  // 这里面classLoader是允许为null的
  @Nullable
  private ClassLoader classLoader;
  // 这个特别重要:ProtocolResolver这个接口事Spring为开发者提供了自定义扩展接口(允许我们自己去介入参与到具体的获取资源的处理上,后面getResouce方法可议看出来)
  // ProtocolResolver接口Spring没有提供任何实现,开发者可议自己实现,从而参与到资源获取的路子上去
  // 备注:这个接口@since 4.3  所以只有Spring4.3后才有这个能力哦~~~
  private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
  private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);
  // ClassLoader可以不指定(一般情况下也不需要指定)
  public DefaultResourceLoader() {
    this.classLoader = ClassUtils.getDefaultClassLoader();
  }
  public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
    this.classLoader = classLoader;
  }
  // 我们可以自己实现一个ProtocolResolver ,然后实现我们自己的获取资源的逻辑~~~下面会有示例
  public void addProtocolResolver(ProtocolResolver resolver) {
    Assert.notNull(resolver, "ProtocolResolver must not be null");
    this.protocolResolvers.add(resolver);
  }
  // @since 5.0  和ASM有关
  public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
    return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());
  }
  ...
  // 这个是核心方法~~~
  @Override
  public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");
    // 首先,Spring会看我们自己有没有实现自己的ProtocolResolver 若有实现,会先以我们自己的为准
    // 备注:它会把ResourceLoader传给开发者,这点特别重要~~~
    for (ProtocolResolver protocolResolver : this.protocolResolvers) {
      Resource resource = protocolResolver.resolve(location, this);
      if (resource != null) {
        return resource;
      }
    }
    // 如果以/打头,就交给getResourceByPath(),注意,它是一个protected方法,子类是可议复写的
    if (location.startsWith("/")) {
      return getResourceByPath(location);
    }
    // 如果以classpath:打头,毫无疑问,交给ClassPathResource
    // 需要注意的是,此处必须`classpath:`  区分大小写的哦
    else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
      return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    }
    // 最后的处理方式:就是当作一个URL来处理
    else {
      try {
        // Try to parse the location as a URL...
        // 如果是文件类型,交给FileUrlResource(@since 5.0.2)
        URL url = new URL(location);
        return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
      }
      catch (MalformedURLException ex) {
        // No URL -> resolve as resource path.
        // fallback  相当于还是交给子类去弄吧
        return getResourceByPath(location);
      }
    }
  }
  // 它是一个protected方法,很多子类都有复写它。可议看到,默认的处理方式是:去classpth里查找这个资源
  // ClassPathContextResource事一个protected内部类 ` extends ClassPathResource implements ContextResource`
  protected Resource getResourceByPath(String path) {
    return new ClassPathContextResource(path, getClassLoader());
  }
}


下面简单给一个使用DefaultResourceLoader去加载资源的Demo:


    public static void main(String[] args) {
        DefaultResourceLoader bf = new DefaultResourceLoader();
        // 自己定义一个  如果是`fsx:`前缀的,那就也从classpath里面去查找这个资源~~~
        bf.addProtocolResolver((String location, ResourceLoader resourceLoader) -> {
            // 可以写自己的实现  返回值是Resource类型即可~~~
            if (location.startsWith("fsx:")) {
                return new ClassPathResource(location.substring("fsx:".length()));
            } else {
                return null; // 若返回null,Spring还是会继续往下走的~~~~
            }
        });
        Resource resource = bf.getResource("spring.properties");
        if (resource.exists()) {
            dumpStream(resource); //name=fangshixiang
        }
    }


再看看DefaultResourceLoader的子类们,实现就相对简单了:


ServletContextResourceLoader


显然它位于web包,和web相关。重写了getResourceByPath()


public class ServletContextResourceLoader extends DefaultResourceLoader {
  private final ServletContext servletContext;
  public ServletContextResourceLoader(ServletContext servletContext) {
    this.servletContext = servletContext;
  }
  // 从写此方法,用ServletContextResource去加载资源~~~~
  @Override
  protected Resource getResourceByPath(String path) {
    return new ServletContextResource(this.servletContext, path);
  }
}

FileSystemResourceLoader

public class FileSystemResourceLoader extends DefaultResourceLoader {
  // 如果以斜杠开头,那就去掉斜杠。并且使用FileSystemResource去装载
  @Override
  protected Resource getResourceByPath(String path) {
    if (path.startsWith("/")) {
      path = path.substring(1);
    }
    return new FileSystemContextResource(path);
  }
}

ClassRelativeResourceLoader

这个出现得稍微晚一点,@since 3.0,实现也是非常的简单。区别在于:setClassLoader(clazz.getClassLoader());并且使用了ClassPathResource去装载资源


AbstractApplicationContext


image.png


这个分支的实现,就很重要了。它直接和应用上下文有关了。

因为AbstractApplicationContext这个类之前非常详细的分析过,因此此处只讲解和ResourceLoader相关的方法


public abstract class AbstractApplicationContext extends DefaultResourceLoader
    implements ConfigurableApplicationContext {
    ...
    // 它没有重写getResource()也没重写getResourceByPath()方法,直接使用的是父类`DefaultResourceLoader`的处理方式(毕竟人家是)Abstract实现,还没有具体指向的~~~
    // 但是有两个方法,可议引起注意~~~
  // ResourcePatternResolver 是 ResourceLoader的子接口
  protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
  }
  // ApplicationContext实现了ResourcePatternResolver接口~~~~
  @Override
  public Resource[] getResources(String locationPattern) throws IOException {
    return this.resourcePatternResolver.getResources(locationPattern);
  }
}


AbstractRefreshableApplicationContext


它没有对任何相关方法重写


AbstractRefreshableConfigApplicationContext


AbstractXmlApplicationContext


上面都没有重写相关方法


FileSystemXmlApplicationContext


它重写一个一个方法:getResourceByPath


public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
  ...
  // 显然根据类名也知道,它使用FileSystemResource去加载xml文件
  @Override
  protected Resource getResourceByPath(String path) {
    if (path.startsWith("/")) {
      path = path.substring(1);
    }
    return new FileSystemResource(path);
  }
}


ClassPathXmlApplicationContext


它没有重写任何方法。为何? 原因很简单:因为DefaultResourceLoader默认就是采用去Classpath里查找资源的方案,所以此处无需重写~~~




相关文章
|
21天前
|
Java API 微服务
【Spring Boot系列】通过OpenAPI规范构建微服务服务接口
【4月更文挑战第5天】通过OpenAPI接口构建Spring Boot服务RestAPI接口
|
4天前
|
Java Spring
spring boot访问接口报500
spring boot访问接口报500
11 2
|
12天前
|
Java 开发者 Spring
Spring Boot中的资源文件属性配置
【4月更文挑战第28天】在Spring Boot应用程序中,配置文件是管理应用程序行为的重要组成部分。资源文件属性配置允许开发者在不重新编译代码的情况下,对应用程序进行灵活地配置和调整。本篇博客将介绍Spring Boot中资源文件属性配置的基本概念,并通过实际示例展示如何利用这一功能。
22 1
|
15天前
|
Java 数据库连接 数据库
spring+mybatis_编写一个简单的增删改查接口
spring+mybatis_编写一个简单的增删改查接口
16 2
|
28天前
|
算法 NoSQL Java
限流艺术:Spring Boot接口限流的实用指南
限流艺术:Spring Boot接口限流的实用指南
60 0
限流艺术:Spring Boot接口限流的实用指南
|
2月前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
53 0
|
2月前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
137 0
|
1月前
|
存储 JSON Java
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
43 2
|
2月前
|
前端开发 搜索推荐 Java
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
|
1月前
|
前端开发 Java 应用服务中间件
Springboot对MVC、tomcat扩展配置
Springboot对MVC、tomcat扩展配置