Spring-资源加载(源码分析)

简介: 在 Java 中,将不同来源的资源抽象成 URL ,通过注册不同的 handler ( URLStreamHandler ) 来处理不同来源的资源的读取逻辑。

在 Java 中,将不同来源的资源抽象成 URL ,通过注册不同的 handler ( URLStreamHandler ) 来处理不同来源的资源的读取逻辑。


然而 URL 没有默认定义相对 Classpath 或 ServletContext 等资源的 handler ,虽然可以注册自己的 URLStreamHandler 来解析特定的 URL 前缀(协议)。但是 URL 也没有提供基本的方法、如检查当前资源是否存在,检查资源是否存在等方法。

URL: 我可以加载各种的资源.......XXX

Spring: 你是个好人


Spring 实现了自己的资源加载策略

  • 职能划分清楚。资源的定义和资源的加载有明确的界限
  • 统一的抽象,统一的资源定义和资源加载策略。


统一资源


org.springframework.core.io.Resource 接口抽象了所有 Spring 内部使用到的底层资源,如 File、URL、Classpath。

public interface Resource extends InputStreamSource {
 .......
 .......
}
复制代码


org.springframework.core.io.InputStreamSource 封装任何能返回 InputStream 的类、如 File、Classpath 下的资源和 ByteArray,该接口只有一个方法 getInputStream

public interface InputStreamSource {
 /**
  * 表示任意形式的资源都可以被转换成输入流、最好每一次调用这个方法的时候都是返回一个新的InputStream
  * 看子类{@link FileSystemResource}符合这个要求
  */
 InputStream getInputStream() throws IOException;
}
复制代码


Resource 接口提供了比 URL 更加多和便捷的方法


idea 截图

对于不同来源的资源文件都有相应的 Resource 实现

  • 文件 FileSystemResource
  • classpath 资源 ClassPathResource
  • url 资源 URLResource
  • InputStream 资源 InputStreamResource
  • byte 数组资源 ByteArrayResource


idea 类图

其中 AbstractResource 实现了 Resource 接口中大多数的方法,如果我们要自定义 Resource ,可以继承这个类,而不是直接实现 Resource

idea 截图


统一资源加载


org.springframework.core.io.ResourceLoader 是 Spring 资源加载的抽象

public interface ResourceLoader {
 /**
  * Pseudo URL prefix for loading from the class path: "classpath:".
  */
 String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
 /**
  * 根据 资源路径 返回一个Resource 句柄、但不确保这个Resource 一定存在、需要调用 Resource exists 方法           * 判断
  * URL位置资源,如”file:C:/test.dat”
  * ClassPath位置资源,如”classpath:test.dat”
  * 相对路径资源,如”WEB-INF/test.dat”,此时返回的Resource实例根据实现不同而不同
  *
  * 要可重用、可多次调用 getInputStream
  */
 Resource getResource(String location);
 @Nullable
 ClassLoader getClassLoader();
}
复制代码

idea 类图


主要实现类就是 DefaultResourceLoader ,其中最核心的方法就是 getResource

@Override
  public Resource getResource(String location) {
   Assert.notNull(location, "Location must not be null");
   // 协议解释
   for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
    Resource resource = protocolResolver.resolve(location, this);
    if (resource != null) {
     return resource;
    }
   }
  // 如果以/ 开头、则创建 ClassPathContextResource、其实也是一个 ClassPathResource 是他的子类、 类路径的资源
  if (location.startsWith("/")) {
   return getResourceByPath(location);
  } else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
   // 如果以 classpath 开头 则 创建ClassPathResource
   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);
   }
  }
 }
复制代码


其中 ProtocolResolver

@FunctionalInterface
public interface ProtocolResolver {
 @Nullable
 Resource resolve(String location, ResourceLoader resourceLoader);
}
复制代码


主要用于处理用户自定义资源协议的,我们可以通过实现此接口来解释我们自定义的资源协议,只需要将实现类对象添加到 DefaultResourceLoader

public void addProtocolResolver(ProtocolResolver resolver) {
        Assert.notNull(resolver, "ProtocolResolver must not be null");
        this.protocolResolvers.add(resolver);
    }
复制代码


ResourcePatternResolver


在 ResourceLoader 接口的基础上增加了 Resource[] getResources(String locationPattern) 方法,支持根据指定的路径匹配模式每次返回多个 Resource 实例。

@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()));
   }
  }
  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)};
   }
  }
 }
复制代码


涉及到的设计模式


面试的时候

面试官: 看过什么框架的源码吗

菜鸡的我: 我看过 Spring 的源码

面试官: 那你说说 Spring 中用到了什么设计模式

菜鸡的我: 额....好像...有....我忘记了


责任链模式


在资源定义和资源加载这一块中,我们可以看到在 DefaultResourceLoader 中的 ProtocolResolver,

private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
public Resource getResource(String location) {
   Assert.notNull(location, "Location must not be null");
   // 协议解释
   for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
    Resource resource = protocolResolver.resolve(location, this);
    if (resource != null) {
     return resource;
    }
   }
      .....
      .....
复制代码


一个加载资源的请求,会被其中一个 ProtocolResolver 去解释成一个 Resource 或者没有一个 ProtocolResolver 能处理这个请求并返回一个 Resource (最终被后面的默认解析成Resource)。这一个过程其实算是使用到了责任链模式的,只是不是一个纯正的责任链模式。


阎宏博士的《JAVA与模式》一书中是这样描述责任链(Chain of Responsibility)模式的

责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。


纯与不纯的责任链模式

  • 纯的责任链模式: 要么处理这个请求、要么完全不处理这个请求并将这个请求传递给下一个处理器;不纯的责任链模式:某个处理器处理了一部分这个请求的业务,然后又将剩下的传递给下一个处理器让其继续处理
  • 纯的责任链模式: 一个请求必须被一个请求处理器所处理;不纯的责任链模式:这个请求可能没有被任何一个处理器处理

而对于怎么存储下一个 handler 的引用,当然可以在当前 handler 中存有下一个 handler 的引用,但是更加常用的还是使用数组或者列表将所有 handler 存储起来,然后进行链式调用处理请求,如 servlet 的filter即使如此。


策略模式


不同的 ResourceLoader 对应着不同的资源加载策略

idea 类图


org.springframework.context.support.GenericApplicationContext 类中

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
 private final DefaultListableBeanFactory beanFactory;
 @Nullable
 private ResourceLoader resourceLoader;
 public void setResourceLoader(ResourceLoader resourceLoader) {
  this.resourceLoader = resourceLoader;
 }
 @Override
 public Resource getResource(String location) {
  if (this.resourceLoader != null) {
   return this.resourceLoader.getResource(location);
  }
    // 最终调用 DefaultResourceLoader 的 getResource
  return super.getResource(location);
 }
复制代码


这个不就是策略模式吗


目录
相关文章
|
1月前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
61 1
|
2天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
1月前
|
监控 IDE Java
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
57 8
|
1月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
38 1
|
1月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
35 1
|
2月前
|
缓存 JavaScript Java
Spring之FactoryBean的处理底层源码分析
本文介绍了Spring框架中FactoryBean的重要作用及其使用方法。通过一个简单的示例展示了如何通过FactoryBean返回一个User对象,并解释了在调用`getBean()`方法时,传入名称前添加`&`符号会改变返回对象类型的原因。进一步深入源码分析,详细说明了`getBean()`方法内部对FactoryBean的处理逻辑,解释了为何添加`&`符号会导致不同的行为。最后,通过具体代码片段展示了这一过程的关键步骤。
Spring之FactoryBean的处理底层源码分析
|
3月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
333 24
|
1月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
30 0
|
3月前
|
XML 存储 Java
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
|
3月前
|
消息中间件 NoSQL 安全
(转)Spring Boot加载 不同位置的 application.properties配置文件顺序规则
这篇文章介绍了Spring Boot加载配置文件的顺序规则,包括不同位置的application.properties文件的加载优先级,以及如何通过命令行参数或环境变量来指定配置文件的名称和位置。
113 0