spring源码问题之统一资源加载策略

首先,Java SE提供了URL类(统一资源定位器),spring为什么还会提供值么一个功能?

URL说是统一资源定位,但基本实现却只限于网络形式发布的资源的查找和定位工作,基本上 只提供了基于HTTP、FTP、File等协议(sun.net.www.protocol包下所支持的协议)的资源定位功能。

在这里插入图片描述
总结:url自身定位太狭隘了。资源这个词范围比较广义:资源能以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在;资源也能存在任何场所,如存在于文件系统、存在于Java应用的Classpath中和url能定位的地方。

Spring中的Resource

Spring提出了一套基于org.springframework.core.io.Resource和 org.springframework.core.io.ResourceLoader接口的资源抽象和加载策略。

之前在构造BeanFactory的时候已经接触过它,如下代码:

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("...")); 

其中ClassPathResource就是Resource的一个特定类型的实现,代表的是存在于Classpath中的资源。

在org.springframework.core.io包下还有如下这些实现类

  • ByteArrayResource。将字节(byte)数组提供的数据作为一种资源进行封装,如果通过 InputStream形式访问该类型的资源,该实现会根据字节数组的数据,构造相应的ByteArray- InputStream并返回
  • ClassPathResource。该实现从Java应用程序的ClassPath中加载具体资源并进行封装,可以使 用指定的类加载器(ClassLoader)或者给定的类进行资源加载。
  • FileSystemResource。对java.io.File类型的封装,所以,我们可以以文件或者URL的形 式对该类型资源进行访问,只要能跟File打的交道,基本上跟FileSystemResource也可以。
  • UrlResource。通过java.net.URL进行的具体资源查找定位的实现类,内部委派URL进行具 体的资源操作。
  • InputStreamResource。将给定的InputStream视为一种资源的Resource实现类,较为少用。 可能的情况下,以ByteArrayResource以及其他形式资源实现代之。

Resource接口定义

public interface Resource extends InputStreamSource { 
 //查询资源状态
 boolean exists(); 
 //查询资源状态
 boolean isOpen(); 
 //访问资源内容
 URL getURL() throws IOException; 
 //访问资源内容
 File getFile() throws IOException; 
 //根据当前资源创建新 的相对资源
 Resource createRelative(String relativePath) throws IOException; 
 
 String getFilename(); 
 
 String getDescription(); 
 
}       

 public interface InputStreamSource 
 {
   InputStream getInputStream() throws IOException; 
 } 
 

ResourceLoader,“更广义的URL”

org.springframework.core.io.ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出。

把ResourceLoader称作统一资源定位器或许 才更恰当一些吧!ResourceLoader定义如下:

/**
 * Spring 将资源的定义和资源的加载区分开了
 * Resource 定义了统一的资源
 * 那资源的加载则由 ResourceLoader 来统一定义
 * ResourceLoader,定义资源加载器,主要应用于根据给定的资源文件地址,返回对应的 Resource
 */
public interface ResourceLoader {

    /** CLASSPATH URL 前缀。默认为:"classpath:" */
    String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

    /**
     * 根据所提供资源的路径 location 返回 Resource 实例
     * 但是它不确保该 Resource 一定存在,需要调用 Resource#exist() 方法来判断
     *
     * 该方法支持以下模式的资源加载:
     * URL位置资源,如 "file:C:/test.dat" 。
     * ClassPath位置资源,如 "classpath:test.dat 。
     * 相对路径资源,如 "WEB-INF/test.dat" ,此时返回的Resource 实例,根据实现不同而不同
     */
    Resource getResource(String location);

    /**
     * Expose the ClassLoader used by this ResourceLoader.
     */
    @Nullable
    ClassLoader getClassLoader();
}

几种有特点的ResourceLoader

DefaultResourceLoader和FileSystemResourceLoader

DefaultResourceLoader的getResource方法如下

/**  
     *ResourceLoader 中最核心的方法为 #getResource(String location)
     * 它根据提供的 location 返回相应的 Resource 。
     * 而 DefaultResourceLoader 对该方法提供了核心实现
     * (因为,它的两个子类都没有提供覆盖该方法,
     *  所以可以断定 ResourceLoader 的资源加载策略就封装在 DefaultResourceLoader 中)
     */
@Override
public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");
    // 首先,通过 ProtocolResolver 来加载资源
    for (ProtocolResolver protocolResolver : this.protocolResolvers) {
        Resource resource = protocolResolver.resolve(location, this);
        if (resource != null) {
            return resource;
        }
    }

    // 其次,以 / 开头,返回 ClassPathContextResource 类型的资源
    if (location.startsWith("/")) {
        return getResourceByPath(location);
    }// 再次,以 classpath: 开头,返回 ClassPathResource 类型的资源
    else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    }// 然后,根据是否为文件 URL ,是则返回 FileUrlResource 类型的资源,否则返回 UrlResource 类型的资源
    else {
        try {
            URL url = new URL(location);
            return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
        }
        catch (MalformedURLException ex) {
            // 最后,返回 ClassPathContextResource 类型的资源
            return getResourceByPath(location);
        }
    }
}

注意:FileSystemResourceLoader是继承 DefaultResourceLoader,且重写了getResourceByPath(String)方法。因此getResourceByPath(String)方法不再是从classpath加载资源并以ClassPathContextResource 类型返回,而是

使getResourceByPath(String)方法从文件系统加载资源并以 FileSystemResource 类型返回


ProtocolResolver

org.springframework.core.io.ProtocolResolver 用户自定义协议资源解决策略,它允许用户自定义资源加载协议。有了 ProtocolResolver 后,我们不需要直接继承 DefaultResourceLoader,改为实现 ProtocolResolver 接口也可以实现自定义的 ResourceLoader

调用 DefaultResourceLoader的addProtocolResolver(ProtocolResolver) 方法加入自定义的ProtocolResolver

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

ProtocolResolver 接口,仅有一个方法 Resource resolve(String location, ResourceLoader resourceLoader)

ResourcePatternResolver和PathMatchingResourcePatternResolver

ResourceLoader 的 Resource getResource(String location) 方法,每次只能根据 location 返回一个 Resource 。org.springframework.core.io.support.ResourcePatternResolver 是 ResourceLoader 的扩展,它支持根据指定的资源路径匹配模式每次返回多个 Resource 实例。

/**
 * ResourceLoader 的 Resource getResource(String location) 方法,
 * 每次只能根据 location 返回一个 Resource 。
 * 当需要加载多个资源时,我们除了多次调用 #getResource(String location) 方法外,别无他法。
 * ResourcePatternResolver 是 ResourceLoader 的扩展,
 * 它支持根据指定的资源路径匹配模式每次返回多个 Resource 实例
 */
public interface ResourcePatternResolver extends ResourceLoader {

    /**
     * 新增了一种新的协议前缀 "classpath*:",该协议前缀由其子类负责实现
     */
    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    /**
     * 支持根据路径匹配模式返回多个 Resource 实例
     */
    Resource[] getResources(String locationPattern) throws IOException;

}

PathMatchingResourcePatternResolver类是ResourcePatternResolver接口的实现类,在处理的时候:

非 “classpath*:” 开头,且路径不包含通配符,直接委托给相应的 ResourceLoader 来实现。
其他情况,调用 #findAllClassPathResources(…)、或 #findPathMatchingResources(…) 方法,返回多个 Resource 。

/**
 * 非 "classpath*:" 开头,且路径不包含通配符,直接委托给相应的 ResourceLoader 来实现。
 * 其他情况,调用 #findAllClassPathResources(...)、或 #findPathMatchingResources(...) 方法
 * 返回多个 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 {// 不以 "classpath*:" 开头
        // 通常只在这里的前缀后面查找模式
        // 而在 Tomcat 上只有在 “*/ ”分隔符之后才为其 “war:” 协议
        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)};
        }
    }
}

/**
 * 当 locationPattern 以 "classpath*:" 开头但是不包含通配符
 * 则调用 #findAllClassPathResources(...) 方法加载资源。
 * 该方法返回 classes 路径下和所有 jar 包中的所有相匹配的资源
 */
protected Resource[] findAllClassPathResources(String location) throws IOException {
    String path = location;
    if (path.startsWith("/")) {
        path = path.substring(1);
    }
    // 真正执行加载所有 classpath 资源
    Set<Resource> result = doFindAllClassPathResources(path);
    if (logger.isTraceEnabled()) {
        logger.trace("Resolved classpath location [" + location + "] to resources " + result);
    }
    // 转换成 Resource 数组返回
    return result.toArray(new Resource[0]);
}

/**
 * 真正执行加载所有 classpath 资源
 */
protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
    Set<Resource> result = new LinkedHashSet<>(16);
    ClassLoader cl = getClassLoader();
    // 根据 ClassLoader 加载路径下的所有资源
    Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
    while (resourceUrls.hasMoreElements()) {
        // 将 URL 转换成 UrlResource
        URL url = resourceUrls.nextElement();
        result.add(convertClassLoaderURL(url));
    }
    if ("".equals(path)) {
        // The above result is likely to be incomplete, i.e. only containing file system references.
        // We need to have pointers to each of the jar files on the classpath as well...
        // 加载路径下得所有 jar 包
        addAllClassLoaderJarRoots(cl, result);
    }
    return result;
}

小结

  • Spring 将资源定义成 Resource,由 ResourceLoader 来加载。举个例子就是 xml 文件的位置 —> ResourceLoader —> Resource的流程。
  • Resource 接口的默认实现是 AbstractResource ,是一个抽象骨架类,它对 Resource 接口做了一个统一的实现。
  • ResourceLoader 接口的默认实现是 DefaultResourceLoader ,在自定义 ResourceLoader 的时候我们除了可以继承该类外还可以实现 ProtocolResolver 接口来实现自定资源加载协议。
  • DefaultResourceLoader 每次只能返回单一的资源,所以 Spring 针对这个提供了另外一个接口 ResourcePatternResolver ,该接口提供了返回多个资源的策略。

是不是已经明白了IoC的第一步定位资源的过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值