前言

我们大体上知道spring可以通过读取xml配置文件,创建创建对象,然后放到ioc容器中,哪在代码层面是如何实现?这个过程用了什么的样的设计思想?本篇主要主要从整体结构上阅读spring的源码,看是如何进行资源定位?

一、统一资源:Resource

org.springframework.core.io.Resource 为 Spring 框架所有资源的抽象和访问接口,它继承 org.springframework.core.io.InputStreamSource接口。作为所有资源的统一抽象,Resource 定义了一些通用的方法,由子类 AbstractResource 提供统一的默认实现。

public interface Resource extends InputStreamSource {
	boolean exists();
	default boolean isReadable() {
		return exists();
	}
	default boolean isOpen() {
		return false;
	}
	default boolean isFile() {
		return false;
	}
	URL getURL() throws IOException;
	URI getURI() throws IOException;
	File getFile() throws IOException;
	default ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}
	long contentLength() throws IOException;
	long lastModified() throws IOException;
	Resource createRelative(String relativePath) throws IOException;
	@Nullable
	String getFilename();
	String getDescription();

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

1.整体结构

在这里插入图片描述

Resource 根据资源的不同类型提供不同的具体实现,如下:

FileSystemResource :对 java.io.File 类型资源的封装,只要是跟 File 打交道的,基本上与 FileSystemResource 也可以打交道。支持文件和 URL 的形式。
ByteArrayResource :对字节数组提供的数据的封装。
UrlResource :对 java.net.URL类型资源的封装。内部委派 URL 进行具体的资源操作。
ClassPathResource :classpath 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源。
其中AbstractResource提供了大部分的实现。如果我们想要实现自定义的 Resource 只需要继承AbstractResource抽象类,然后根据当前的具体资源特性覆盖相应的方法即可。

Spring 将资源的定义和资源的加载区分开,Resource 定义了统一的资源,资源的加载则由 ResourceLoader来统一定义。

二、ResourceLoader

ResourceLoader定义资源加载器,主要应用于根据给定的资源文件地址,返回对应的 Resource

public interface ResourceLoader {
	/** Pseudo URL prefix for loading from the class path: "classpath:". */
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
	Resource getResource(String location);
	@Nullable
	ClassLoader getClassLoader();
}

#getResource(String location) 方法,根据所提供资源的路径 location 返回 Resource实例。
该方法支持以下模式的资源加载:
URL位置资源,如 "file:C:/test.dat"
ClassPath位置资源,如 "classpath:test.dat
相对路径资源,如 "WEB-INF/test.dat" ,此时返回的Resource 实例,根据实现不同而不同。
该方法的主要实现是在其子类 DefaultResourceLoader 中实现

1.整体结构

在这里插入图片描述

2.方法getResource

org.springframework.core.io.DefaultResourceLoaderResourceLoader 的默认实现。其getResource(String location)如下

// DefaultResourceLoader.java

@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 {
            // 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) {
            // 最后,返回 ClassPathContextResource 类型的资源
            // No URL -> resolve as resource path.
            return getResourceByPath(location);
        }
    }
}

3.ResourcePatternResolver

另外需要关注的是:
ResourceLoaderResource getResource(String location) 方法,每次只能根据 location 返回一个 Resource 。其子类org.springframework.core.io.support.ResourcePatternResolver在它的基础上进行了扩展,支持根据指定的资源路径匹配模式每次返回多个 Resource 实例,其定义如下:

public interface ResourcePatternResolver extends ResourceLoader {

	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

	Resource[] getResources(String locationPattern) throws IOException;

}

org.springframework.core.io.support.PathMatchingResourcePatternResolver ,为 ResourcePatternResolver 最常用的子类,它除了支持 ResourceLoaderResourcePatternResolver 新增的 classpath*: 前缀外,还支持 Ant 风格的路径匹配模式(类似于 “**/*.xml”)。

/**
 * 内置的 ResourceLoader 资源定位器
 */
private final ResourceLoader resourceLoader;
/**
 * Ant 路径匹配器
 */
private PathMatcher pathMatcher = new AntPathMatcher();

public PathMatchingResourcePatternResolver() {
	this.resourceLoader = new DefaultResourceLoader();
}

public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
	Assert.notNull(resourceLoader, "ResourceLoader must not be null");
	this.resourceLoader = resourceLoader;
}

public PathMatchingResourcePatternResolver(@Nullable ClassLoader classLoader) {
	this.resourceLoader = new DefaultResourceLoader(classLoader);
}
@Override
	public Resource getResource(String location) {
		return getResourceLoader().getResource(location);
	}

ResourceLoader 在实例化的时候,可以指定一个 ResourceLoader,并在getResource方法中使用了该ResourceLoader,如果不提供,则默认使用DefaultResourceLoader
PathMatchingResourcePatternResolver最大的不同是提供了getResources方法的实现。

@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()));
        }
    // 不以 "classpath*:" 开头
    } else {
        // Generally only look for a pattern after a prefix here, // 通常只在这里的前缀后面查找模式
        // and on Tomcat only after the "*/" separator for its "war:" protocol. 而在 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)};
        }
    }
}

这里不展开对getResources方法的分析。我们重点关注,spring是如何使用ResourceResourceLoader

三、spring如何定位资源?

1.分析入口

创建一个测试主函数

public static void main(String[] args) {
		ApplicationContext context =
				new ClassPathXmlApplicationContext("classpath*:application.xml");
		UserService userService = context.getBean(UserService.class);
		System.out.println(userService);
		// 这句将输出: hello world
		System.out.println(userService.getName());
	}

2.流程跟踪

在这里插入图片描述
这里先暂且忽略其他流程,一致找到加载xml文件的地方
在这里插入图片描述
上面的流程可通过端点跟踪或者是ClassPathXmlApplicationContext类的继承结构找到对应的实现。
在这里插入图片描述

这里重要的一个地方是beanDefinitionReader.setResourceLoader(this);,也就是说AbstractXmlApplicationContext及其子类也属于ResourceLoader。继续跟踪
在这里插入图片描述这里到AbstractBeanDefinitionReader#loadBeanDefinitions方法后,就是我们的目的地了,下面来分析该方法

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
		// 获取加载器 也就是上面的beanDefinitionReader.setResourceLoader(this);
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
		}

		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				int count = loadBeanDefinitions(resources);
				if (actualResources != null) {
					Collections.addAll(actualResources, resources);
				}
				if (logger.isTraceEnabled()) {
					logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
				}
				return count;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			// Can only load single resources by absolute URL.
			Resource resource = resourceLoader.getResource(location);
			int count = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
			}
			return count;
		}
	}

在分析上面方法之前,先来看一张类关系图
在这里插入图片描述
从上面结构图中可以看出,我们创建的ClassPathXmlApplicationContext也实现了ResourceLoader,并且判断resourceLoader instanceof ResourcePatternResolver为true.
ClassPathXmlApplicationContextgetResource最终又委托给了ResourcePatternResolver进行实现
在这里插入图片描述
ResourcePatternResolver再根据不同的location匹配返回不同的Resource对象

总结

  1. 不同的资源路径(location),对应不同的Resource对象
  2. 根据location,由ResourceLoader进行加载,返回不同的Resouce对象
  3. ResourcePatternResolverResourceLoader进行了增强,可以处理多个location
  4. ClassPathXmlApplicationContext也实现了ResourceLoader,但并不直接获取Resource,而是委托给了ResourcePatternResolver进行处理

到这里,还有一点没有弄清楚,ClassPathXmlApplicationContext为什么要实现ResourceLoader呢?

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐