Loading

Spring系列-1.2 Resource解析

Spring版本:Spring 5.2.9.BUILD-SNAPSHOT

修改过部分源码,但不影响主体流程

Resource简介

Spring对各种底层资源,比如文件系统中的一个文件,classpath上的一个文件,或者一个网络URL,统一抽象为接口`Resource``来表示

源加载策略需要满足如下要求:

  • 职能划分清楚。资源的定义和资源的加载应该要有一个清晰的界限。

  • 统一的抽象,统一的资源定义和资源加载策略。资源加载后要返回统一的抽象给客户端,客户端要对资源进行怎样的处理,应该由抽象资源接口来界定。

实现类

Resource接口继承了InputStreamSource接口通过getInputStream()方法获取文件输入流。

下面是Resource接口继承图

实现类 介绍
ClassPathResource Java类路径上的一个资源文件,比如一个class文件,xml/properties配置文件
FileSystemResource 操作系统文件系统中的一个资源文件
UrlResource 将一个网络URL地址封装成一个资源文件
ByteArrayResource 将一个字节数组封装成一个Resource
ServletContextResource 获取ServletContext环境下的资源文件,在web应用程序的根目录下解释相对路径
InputStreamResource 将一个输入流封装成一个资源,因为用于描述一个已经打开的资源
VfsResource 针对JBoss VFS 的资源

ResourceLoader

  • Resource 定义了统一的资源,那资源的加载则由 ResourceLoader 来统一定义。

  • 通过ResourceLoader,给定其可以接受的资源路径,我们可以获得对应资源的Resource对象,然后进行进行相应的资源访问。

接口定义如下:

    public interface ResourceLoader {
        String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
    
        Resource getResource(String location);
    
        ClassLoader getClassLoader();
    }

getResource()根据所提供资源的路径 location 返回 Resource 实例,但是它不确保该 Resource 一定存在,需要调用 Resource.exist()方法判断。该方法支持以下模式的资源加载:

  • URL位置资源,如"file:C:/test.dat"

  • ClassPath位置资源,如"classpath:test.dat"

  • 相对路径资源,如"WEB-INF/test.dat",此时返回的Resource实例根据实现不同而不同

该方法的主要实现是在其子类 DefaultResourceLoader 中实现。

getClassLoader() 返回 ClassLoader 实例,对于想要获取 ResourceLoader 使用的 ClassLoader 用户来说,可以直接调用该方法来获取。

实现类

DefaultResourceLoader

核心实现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;
			}
		}

		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}
		// 处理带有classpath标识的resource
		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标识的resource定位
				URL url = new URL(location);
				return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				// 如果既不是classpath,也不是url标识的resource定位,则把getResource的重任交给getResourceByPath,
				// 此方法是protected方法,默认的实现是得到一个ClassPathContextResource,此方法常常会用子类来实现
				return getResourceByPath(location);
			}
		}
	}

流程

DefaultResourceLoader会以以下顺序识别路径 :

  1. 轮询每个ProtocolResolver看它们哪个可以处理该路径,如果可以让其处理并返回相应的资源;

  2. 如果路径以"/"开头,尝试将其处理为一个ClassPathContextResource并返回;

  3. 如果路径以"classpath:"前缀开头,去除路径中前缀部分之后将其封装成一个ClassPathResource并返回;

  4. 如果路径是URL格式,如果路径以"file:"/“vfs:”/"vfsfile:"前缀开头,将其封装成一个FileUrlResource并返回;否则将其封装成一个UrlResource返回;

5.其他格式,仍然尝试将其封装一个ClassPathContextResource并返回;

ResourcePatternResolver

使用DefaultResourceLoader每次只能根据location返回一个Resource实例,当需要加载多个资源时就需要多次调用,ResourcePatternResolver便是ResourceLoader的扩展,它支持根据指定的资源路径匹配模式每次返回多个Resource实例。

核心定义

    public interface ResourcePatternResolver extends ResourceLoader {

         /**
	 * 在所有根目录下搜索文件的伪URL的前缀
	 * 与ResourceLoader中classpath不同的地方在于,此前缀会在所有的JAR包的根目录下搜索指定文件。
	 * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
	 */
        String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    	/**
	 * 返回指定路径下所有的资源对象。
	 * 返回的对象集合应该有Set的语义,也就是说,对于同一个资源,只应该返回一个资源对象
	 * @param locationPattern the location pattern to resolve
	 * @return the corresponding Resource objects
	 * @throws IOException in case of I/O errors
	 */
        Resource[] getResources(String locationPattern) throws IOException;
    }

PathMatchingResourcePatternResolver

核心实现PathMatchingResourcePatternResolver#getResources

	@Override
	public Resource[] getResources(String locationPattern) throws IOException {
		Assert.notNull(locationPattern, "Location pattern must not be null");
		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)};
			}
		}
	}

处理流程:

posted @ 2022-01-26 15:02  xmz_pc  阅读(89)  评论(0编辑  收藏  举报