06--SpringIoC容器初始化资源文件读取(二)

上一篇介绍了通过ClassPathResource类加载资源文件,在Spring里还有一个比较重要的加载资源文件的类DefaultResourceLoader,这一篇介绍下DefaultResourceLoader的使用,该类的继承关系很简单,只继承了ResourceLoader接口,不再粘图说明,新建一个测试用例

@Test
public void testDefaultResourceLoader1() throws IOException {
    Resource resource = new DefaultResourceLoader().getResource
            ("org/springframework/beans/factory/xml/simplePropertyNamespaceHandlerWithExpressionLanguageTests.xml");
    print(resource);
}

测试用例很简单,创建DefaultResourceLoader对象并调用其getResource方法读取资源文件

DefaultResourceLoader的源码:

public class DefaultResourceLoader implements ResourceLoader {

    //类加载器
    @Nullable
    private ClassLoader classLoader;

    //协议解析器的集合
    private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);

    //资源缓存
    private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);


    /**
     * 默认构造函数,优先使用线程上下文类加载器进行
     * Create a new DefaultResourceLoader.
     * <p>ClassLoader access will happen using the thread context class loader
     * at the time of this ResourceLoader's initialization.
     *
     * @see java.lang.Thread#getContextClassLoader()
     */
    public DefaultResourceLoader() {
        this.classLoader = ClassUtils.getDefaultClassLoader();
    }

    /**
     * 构造函数
     * Create a new DefaultResourceLoader.
     *
     * @param classLoader the ClassLoader to load class path resources with, or {@code null}
     *                    for using the thread context class loader at the time of actual resource access
     */
    public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
        this.classLoader = classLoader;
    }


    /**
     * 设置classLoader
     * Specify the ClassLoader to load class path resources with, or {@code null}
     * for using the thread context class loader at the time of actual resource access.
     * <p>The default is that ClassLoader access will happen using the thread context
     * class loader at the time of this ResourceLoader's initialization.
     */
    public void setClassLoader(@Nullable ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    /**
     * 返回ClassLoader,如果ClassLoader不存在则创建并返回
     * Return the ClassLoader to load class path resources with.
     * <p>Will get passed to ClassPathResource's constructor for all
     * ClassPathResource objects created by this resource loader.
     *
     * @see ClassPathResource
     */
    @Override
    @Nullable
    public ClassLoader getClassLoader() {
        return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
    }

    /**
     * 注册解析协议
     * Register the given resolver with this resource loader, allowing for
     * additional protocols to be handled.
     * <p>Any such resolver will be invoked ahead of this loader's standard
     * resolution rules. It may therefore also override any default rules.
     *
     * @see #getProtocolResolvers()
     * @since 4.3
     */
    public void addProtocolResolver(ProtocolResolver resolver) {
        Assert.notNull(resolver, "ProtocolResolver must not be null");
        this.protocolResolvers.add(resolver);
    }

    /**
     * 返回协议解析集合
     * Return the collection of currently registered protocol resolvers,
     * allowing for introspection as well as modification.
     *
     * @since 4.3
     */
    public Collection<ProtocolResolver> getProtocolResolvers() {
        return this.protocolResolvers;
    }

    /**
     * 根据valueType返回对应缓存资源
     * Obtain a cache for the given value type, keyed by {@link Resource}.
     *
     * @param valueType the value type, e.g. an ASM {@code MetadataReader}
     * @return the cache {@link Map}, shared at the {@code ResourceLoader} level
     * @since 5.0
     */
    @SuppressWarnings("unchecked")
    public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
        return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());
    }

    /**
     * 清楚所有resourceCaches缓存
     * Clear all resource caches in this resource loader.
     *
     * @see #getResourceCache
     * @since 5.0
     */
    public void clearResourceCaches() {
        this.resourceCaches.clear();
    }


    /**
     * 根据指定路径加载资源
     * 1.URL位置资源,如”file:C:/test.dat”
     * 2.ClassPath位置资源,如”classpath:test.dat”
     * 3.相对路径资源,如”WEB-INF/test.dat”,此时返回的Resource实例根据实现不同而不同。
     *
     * 注意:调用此方法时并不意味着路径下一定存在对应的资源,可以先调用Resource.exists()来检查是否存在,然后再实际读取资源文件
     *
     * @param location the resource location
     * @return
     */
    @Override
    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");

        //优先遍历协议解决器集,如果可以解决,则返回位置相应的资源
        for (ProtocolResolver protocolResolver : this.protocolResolvers) {
            Resource resource = protocolResolver.resolve(location, this);
            if (resource != null) {
                return resource;
            }
        }

        //如果资源位置以"/"开头,则获取路径资源
        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());
        } else {
            try {
                // Try to parse the location as a URL...
                // 尝试将路径转换为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.
                // 没有成功转换为URL资源,则将location视为资源路径并返回对应解析资源
                return getResourceByPath(location);
            }
        }
    }

    /**
     * Return a Resource handle for the resource at the given path.
     * <p>The default implementation supports class path locations. This should
     * be appropriate for standalone implementations but can be overridden,
     * e.g. for implementations targeted at a Servlet container.
     *
     * @param path the path to the resource
     * @return the corresponding Resource handle
     * @see ClassPathResource
     * @see org.springframework.context.support.FileSystemXmlApplicationContext#getResourceByPath
     * @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath
     */
    protected Resource getResourceByPath(String path) {
        return new ClassPathContextResource(path, getClassLoader());
    }


    /**
     * ClassPathResource that explicitly expresses a context-relative path
     * through implementing the ContextResource interface.
     */
    protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {

        public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
            super(path, classLoader);
        }

        @Override
        public String getPathWithinContext() {
            return getPath();
        }

        @Override
        public Resource createRelative(String relativePath) {
            String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
            return new ClassPathContextResource(pathToUse, getClassLoader());
        }
    }

}
1.创建DefaultResourceLoader对象
public DefaultResourceLoader() {
    //获取classLoader对象
    this.classLoader = ClassUtils.getDefaultClassLoader();
}

获取classLoader对象可查看上文讲解05–Spring IoC容器资源文件读取(一)

2.加载资源
public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");
    //优先遍历协议解决器集,如果可以解决,则返回位置相应的资源
    for (ProtocolResolver protocolResolver : this.protocolResolvers) {
        Resource resource = protocolResolver.resolve(location, this);
        if (resource != null) {
            return resource;
        }
    }
    //如果资源位置以"/"开头,则获取相对路径资源
    if (location.startsWith("/")) {
        return getResourceByPath(location);
    }
    //如果资源位置以"classpath:"开头,则获取类路径下资源
    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资源,例如file:C:/test.dat
            URL url = new URL(location);
            return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
        } catch (MalformedURLException ex) {
            // No URL -> resolve as resource path.
            // 没有通过上述策略加载资源,且没有成功转换为URL资源,则将location视为相对路径资源进行加载
            return getResourceByPath(location);
        }
    }
}

可以看到,加载资源也是分步骤进行的

  • 优先遍历协议解析器
  • 获取/开头的相对路径资源
  • 获取classpath类路径下资源
  • 尝试获取URL资源
  • 再次尝试获取相对路径下资源
3.获取InputStream
public InputStream getInputStream() throws IOException {
    InputStream is;
    //如果类对象新不为null,则使用类对象信息的getResourceAsStream获取输入流
    if (this.clazz != null) {
        is = this.clazz.getResourceAsStream(this.path);
    }
    //如果类加载器不为null,则使用类加载器的getResourceAsStream获取输入流
    else if (this.classLoader != null) {
        is = this.classLoader.getResourceAsStream(this.path);
    }
    else {
        //否则使用ClassLoader类的getSystemResourceAsStream方法获取输入流
        is = ClassLoader.getSystemResourceAsStream(this.path);
    }
    if (is == null) {
        //以上三种方法都无法获取到输入流的话,那么说明文件不存在,抛出异常
        throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
    }
    return is;
}

大部分的过程与上文相似,可结合上文来分析,
另外在DefaultResourceLoader新加了一个自定义协议解析器,再来分析下该解析器的使用

4.ProtocolResolver 自定义协议解析器
package com.lyc.cn;

import org.springframework.core.io.ProtocolResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

public class MyProtocolResolver implements ProtocolResolver {

    @Override
    public Resource resolve(String location, ResourceLoader resourceLoader) {
        if (location.startsWith("my")) {
            return resourceLoader.getResource(location.replace("my", "classpath"));
        }
        return null;
    }
}

/**
 * 自定义Protocol加载资源文件
 */
@Test
public void test5() {
    DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
    resourceLoader.addProtocolResolver(new MyProtocolResolver());
    Resource resource = resourceLoader.getResource("my:/resources/import-into-idea.md");
    print(resource);
}

resourceLoader.getResource(location.replace("my", "classpath")); 自定义协议解析器只是在自定义方法里将”classpath”与自定义的”my”进行了替换,其他的解析过程,与我们之前分析的都是相同的

ClassPathResource和DefaultResourceLoader的使用就到此为止了

猜你喜欢

转载自blog.csdn.net/lyc_liyanchao/article/details/82384376
今日推荐