概述
IOC之Spring统一加载策略
1. 概述
资源是一个抽象的概念,什么是资源?我们已知Spring
中有很多xml
配置文件,同时还可能自建各种properties
资源文件,还有可能进行网络交互,收发各种文件、二进制流等。
资源粗略的可以分为(这里以Spring
的分类为例):
URL资源
File资源
ClassPath相关资源
服务器相关资源(JBoss AS 5.x上的VFS资源)
通过sun
的API
可以实现的资源访问,在 Sun
所提供的标准 API
里,资源访问通常由 java.net.URL
和文件 IO
来完成,尤其是当我们需要访问来自网络的资源时,通常会选择 URL
类。
URL
类可以处理一些常规的资源访问问题,但依然不能很好地满足所有底层资源访问的需要,比如,暂时还无法从类加载路径、或相对于 ServletContext
的路径来访问资源,虽然 Java
允许使用特定的 URL
前缀注册新的处理类(例如已有的 http: 前缀的处理类
),但是这样做通常比较复杂,而且 URL
接口还缺少一些有用的功能,比如检查所指向的资源是否存在等
//访问ClassPath中的资源
URL resourceURL = getClassLoader().getResource("java/lang/String.class");
InputStream resourceContent = getClassLoader().getResourceAsStream("java/lang/String.class");
//访问文件系统中的资源
File resourceFile = new File("c:\test.txt");
InputStream resourceContent = new FileInputStream(resourceFile);
//访问web应用中的资源
URL resourceURL = servletContext.getResource("/WEB-INF/web.xml");
InputStream resourceContent = servletContext.getResourceAsStream("/WEB-INF/web.xml");
//访问jar包中的资源
URL jarURL = new File(System.getProperty("java.home") + "/lib/rt.jar").toURI().toURL();
URL resourceURL = new URL("jar:" + jarURL + "!/java/lang/String.class");
InputStream resourceContent = resourceURL.openStream();
java.net.URL
的局限性迫使 Spring
必须实现自己的资源加载策略,该资源加载策略需要满足如下要求:
① 职能划分清楚,资源的定义和资源的加载应该要有一个清晰的界限;
② 统一的抽象,统一的资源定义和资源加载策略。资源加载后要返回统一的抽象给客户端,客户端要对资源进行怎样的处理,应该由抽象资源接口来界定;
2. 统一资源 Resource
org.springframework.core.io.Resource
为 Spring
框架所有资源的抽象和访问接口,它继承 org.springframework.core.io.InputStreamSource
接口,作为所有资源的统一抽象,Source
定义了一些通用的方法,由子类 AbstractResource
提供统一的默认实现
public interface Resource extends InputStreamSource {
//资源是否存在
boolean exists();
//资源是否可读
default boolean isReadable() {
return true;
}
//资源所代表的句柄是否被一个stream打开了
default boolean isOpen() {
return false;
}
//是否为 File
default boolean isFile() {
return false;
}
//返回资源的URL的句柄
URL getURL() throws IOException;
//返回资源的URI的句柄
URI getURI() throws IOException;
//返回资源的File的句柄
File getFile() throws IOException;
//返回 ReadableByteChannel
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();
}
他的继承关系如下:
//EncodResource
//主要实现了对资源文件的编码
//AbstractResource
//`AbstractResource` 为 `Resource` 接口的默认实现,它实现了 `Resource` 接口的大部分的公共实现
//FileSystemResource:
//对 java.io.File 类型资源的封装,只要是跟 File 打交道的,基本上与 FileSystemResource 也可以打交道。支持文件和 URL 的形式,实现 WritableResource 接口,且从 Spring Framework 5.0 开始,FileSystemResource 使用NIO.2 API进行读/写交互
//ByteArrayResource:
//对字节数组提供的数据的封装。如果通过 InputStream 形式访问该类型的资源,该实现会根据字节数组的数据构造一个相应的 ByteArrayInputStream。
//UrlResource:
//对 java.net.URL类型资源的封装。内部委派 URL 进行具体的资源操作。
//ClassPathResource:
//class path 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源。
//InputStreamResource:
//将给定的 InputStream 作为一种资源的 Resource 的实现类
AbstractResource
为 Resource
接口的默认实现,它实现了 Resource
接口的大部分的公共实现
public abstract class AbstractResource implements Resource {
/**
* 判断文件是否存在,若判断过程产生异常(因为会调用SecurityManager来判断),就关闭对应的流
*/
@Override
public boolean exists() {
try {
return getFile().exists();
}
catch (IOException ex) {
// Fall back to stream existence: can we open the stream?
try {
InputStream is = getInputStream();
is.close();
return true;
}
catch (Throwable isEx) {
return false;
}
}
}
/**
* 直接返回true,表示可读
*/
@Override
public boolean isReadable() {
return true;
}
/**
* 直接返回 false,表示未被打开
*/
@Override
public boolean isOpen() {
return false;
}
/**
* 直接返回false,表示不为 File
*/
@Override
public boolean isFile() {
return false;
}
/**
* 抛出 FileNotFoundException 异常,交给子类实现
*/
@Override
public URL getURL() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
}
/**
* 基于 getURL() 返回的 URL 构建 URI
*/
@Override
public URI getURI() throws IOException {
URL url = getURL();
try {
return ResourceUtils.toURI(url);
}
catch (URISyntaxException ex) {
throw new NestedIOException("Invalid URI [" + url + "]", ex);
}
}
/**
* 抛出 FileNotFoundException 异常,交给子类实现
*/
@Override
public File getFile() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
}
/**
* 根据 getInputStream() 的返回结果构建 ReadableByteChannel
*/
@Override
public ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
/**
* 获取资源的长度
*
* 这个资源内容长度实际就是资源的字节长度,通过全部读取一遍来判断
*/
@Override
public long contentLength() throws IOException {
InputStream is = getInputStream();
try {
long size = 0;
byte[] buf = new byte[255];
int read;
while ((read = is.read(buf)) != -1) {
size += read;
}
return size;
}
finally {
try {
is.close();
}
catch (IOException ex) {
}
}
}
/**
* 返回资源最后的修改时间
*/
@Override
public long lastModified() throws IOException {
long lastModified = getFileForLastModifiedCheck().lastModified();
if (lastModified == 0L) {
throw new FileNotFoundException(getDescription() +
" cannot be resolved in the file system for resolving its last-modified timestamp");
}
return lastModified;
}
protected File getFileForLastModifiedCheck() throws IOException {
return getFile();
}
/**
* 交给子类实现
*/
@Override
public Resource createRelative(String relativePath) throws IOException {
throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
}
/**
* 获取资源名称,默认返回 null
*/
@Override
@Nullable
public String getFilename() {
return null;
}
/**
* 返回资源的描述
*/
@Override
public String toString() {
return getDescription();
}
@Override
public boolean equals(Object obj) {
return (obj == this ||
(obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription())));
}
@Override
public int hashCode() {
return getDescription().hashCode();
}
}
如果我们想要实现自定义的 Resource
,继承 AbstractResource
抽象类,然后根据当前的具体资源特性覆盖相应的方法即可
3. 统一资源定位 ResourceLoader
之前说到Spring
将资源的定义和资源的加载区分开了,Resource
定义了统一的资源,那资源的加载则由 ResourceLoader
来统一定义
org.springframework.core.io.ResourceLoader
为 Spring
资源加载的统一抽象,具体的资源加载则由相应的实现类来完成,所以我们可以将 ResourceLoader
称作为统一资源定位器;
同时在 ResourceLoader
中有getResource(String location);
方法,它可以直接根据我们传入的资源路径的格式,生成对应的Resource
,而不用我们显示的生成Resource
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
//根据所提供资源的路径 location 返回 Resource 实例,而不用我们显示的声明Resource,该方法的主要实现是在其子类 DefaultResourceLoader 中实现
Resource getResource(String location);
//返回 ClassLoader 实例,对于想要获取 ResourceLoader 使用的 ClassLoader 用户来说,可以直接调用该方法来获取,有的ResourceLoader使用了自定义的类加载器
ClassLoader getClassLoader();
}
3.1 DefaultResourceLoader
DefaultResourceLoader
是 ResourceLoader
的默认实现,它接收 ClassLoader
作为构造函数的参数或者使用不带参数的构造函数,在使用不带参数的构造函数时,使用的 ClassLoader
为默认的 ClassLoader
(一般为Thread.currentThread().getContextClassLoader()
),可以通过 ClassUtils.getDefaultClassLoader()
获取。当然也可以调用 setClassLoader()
方法进行后续设置
public class DefaultResourceLoader implements ResourceLoader {
private ClassLoader classLoader;
//ProtocolResolver ,用户自定义协议资源解决策略,实现ProtocolResolver 接口也可以实现自定义的 ResourceLoader
private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<ProtocolResolver>(4);
public DefaultResourceLoader() {
this.classLoader = ClassUtils.getDefaultClassLoader();
}
public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
public void setClassLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
@Override
public Resource getResource(String location) {
.....
可以看到成员变量中有一个ProtocolResolver
的set
集合,ProtocolResolver
是用户自定义协议资源解决策略,也就是说除了直接继承 DefaultResourceLoader
,实现 ProtocolResolver
接口也可以实现自定义的 ResourceLoader
public interface ProtocolResolver {
Resource resolve(String location, ResourceLoader resourceLoader);
}
ProtocolResolver
接口仅有一个方法 Resource resolve(String location, ResourceLoader resourceLoader)
,该方法接收两个参数:资源路径location
,指定的加载器 ResourceLoader
,返回为相应的 Resource
。在 Spring 中你会发现该接口并没有实现类,它需要用户自定义,自定义的 Resolver
如何加入 Spring
体系呢?调用 DefaultResourceLoader.addProtocolResolver()
即可
接着看 DefaultResourceLoader
,在ResourceLoader
中最核心的方法为 getResource()
,它根据提供的 location
返回相应的 Resource
,而 DefaultResourceLoader
对该方法提供了核心实现(它的两个子类都没有提供覆盖该方法,所以可以断定ResourceLoader
的资源加载策略就封装 DefaultResourceLoader
中)
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//首先通过 ProtocolResolver 来加载资源,成功返回 Resource
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
//若 location 以 / 开头,则调用 getResourceByPath()构造 ClassPathContextResource 类型资源并返回
if (location.startsWith("/")) {
return getResourceByPath(location);
}
//若 location 以 classpath: 开头,则构造 ClassPathResource 类型资源并返回,在构造该资源时,通过 getClassLoader()获取当前的 ClassLoader
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
//构造 URL ,尝试通过它进行资源定位,若没有抛出 MalformedURLException 异常,则判断是否为 FileURL , 如果是则构造 FileUrlResource 类型资源,否则构造 UrlResource。若在加载过程中抛出 MalformedURLException 异常,则委派 getResourceByPath() 实现资源定位加载
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
来加载资源,也就是自定义的加载策略,成功返回 Resource
,接下来根据传入的location
的格式选择相对应的classLoader
,返回相对应的Resource
,这里使用的是策略模式;
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource fileResource1 = resourceLoader.getResource("D:/Users/chenming673/Documents/spark.txt");
System.out.println("fileResource1 is FileSystemResource:" + (fileResource1 instanceof FileSystemResource));//false
//D:/Users/chenming673/Documents/spark.txt资源其实在该方法中没有相应的资源类型,那么它就会在抛出 MalformedURLException 异常时通过 getResourceByPath() 构造一个 ClassPathResource 类型的资源。而指定有协议前缀的资源路径,则通过 URL 就可以定义,所以返回的都是UrlResource类型
Resource fileResource2 = resourceLoader.getResource("/Users/chenming673/Documents/spark.txt");
System.out.println("fileResource2 is ClassPathResource:" + (fileResource2 instanceof ClassPathResource));//true
Resource urlResource1 = resourceLoader.getResource("file:/Users/chenming673/Documents/spark.txt");
System.out.println("urlResource1 is UrlResource:" + (urlResource1 instanceof UrlResource));//true
Resource urlResource2 = resourceLoader.getResource("http://www.baidu.com");
System.out.println("urlResource1 is urlResource:" + (urlResource2 instanceof UrlResource));//true
3.2 FileSystemResourceLoader
从上面的示例我们看到,其实 DefaultResourceLoader
对getResourceByPath(String)
方法处理其实不是很恰当,这个时候我们可以使用 FileSystemResourceLoader
,它继承 DefaultResourceLoader
且覆写了 getResourceByPath(String)
,使之从文件系统加载资源并以 FileSystemResource
类型返回,这样我们就可以得到想要的资源类型
@Override
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemContextResource(path);
}
FileSystemContextResource
为 FileSystemResourceLoader
的内部类,它继承 FileSystemResource
private static class FileSystemContextResource extends FileSystemResource implements ContextResource {
public FileSystemContextResource(String path) {
super(path);
}
@Override
public String getPathWithinContext() {
return getPath();
}
}
3.3 ResourcePatternResolver
ResourceLoader
的 Resource getResource(String location)
每次只能根据 location
返回一个 Resource
,当需要加载多个资源时,我们除了多次调用 getResource()
外别无他法。ResourcePatternResolver
是 ResourceLoader
的扩展,它支持根据指定的资源路径匹配模式每次返回多个 Resource
实例,其定义如下
public interface ResourcePatternResolver extends ResourceLoader {
//新的协议前缀 classpath*:,该协议前缀由其子类负责实现
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
//在 ResourceLoader 的基础上增加了 getResources(String locationPattern),以支持根据路径匹配模式返回多个 Resource 实例
Resource[] getResources(String locationPattern) throws IOException;
}
PathMatchingResourcePatternResolver
为 ResourcePatternResolver
最常用的子类,它除了支持 ResourceLoader
和 ResourcePatternResolver
新增的 classpath*:
前缀外,还支持 Ant 风格的路径匹配模式(类似于 **/*.xml
)
PathMatchingResourcePatternResolver
提供了三个构造方法,如下:
private final ResourceLoader resourceLoader;
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);
}
PathMatchingResourcePatternResolver
在实例化的时候,可以指定一个 ResourceLoader
,如果不指定的话,它会在内部构造一个 DefaultResourceLoader
getResource()
方法直接委托给相应的 ResourceLoader
来实现,所以如果我们在实例化的 PathMatchingResourcePatternResolver
的时候,如果不知道 ResourceLoader
,那么在加载资源时,其实就是 DefaultResourceLoader
的过程
@Override
public Resource getResource(String location) {
return getResourceLoader().getResource(location);
}
Resource[] getResources(String locationPattern)
也相同,只不过返回的资源时多个而已
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
// 以 classpath*: 开头
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// 路径包含通配符
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
return findPathMatchingResources(locationPattern);
}
else {
// 路径不包含通配符
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
// 路径包含通配符
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
return findPathMatchingResources(locationPattern);
}
else {
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
下面就 findAllClassPathResources()
做详细分析,当 locationPattern
以 classpath*:
开头但是不包含通配符,则调用findAllClassPathResources()
方法加载资源。该方法返回 classes
路径下和所有 jar
包中的所有相匹配的资源
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (path.startsWith("/")) {
path = path.substring(1);
}
Set<Resource> result = doFindAllClassPathResources(path);
if (logger.isDebugEnabled()) {
logger.debug("Resolved classpath location [" + location + "] to resources " + result);
}
return result.toArray(new Resource[0]);
}
真正执行加载的是在 doFindAllClassPathResources()
方法,如下
protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
//根据 ClassLoader 加载路径下的所有资源
Set<Resource> result = new LinkedHashSet<>(16);
ClassLoader cl = getClassLoader();
//如果在构造 PathMatchingResourcePatternResolver 实例的时候如果传入了 ClassLoader,则调用其 getResources()
//否则调用ClassLoader.getSystemResources(path)
Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
while (resourceUrls.hasMoreElements()) {
URL url = resourceUrls.nextElement();
result.add(convertClassLoaderURL(url));
}
if ("".equals(path)) {
addAllClassLoaderJarRoots(cl, result);
}
return result;
}
doFindAllClassPathResources()
根据 ClassLoader
加载路径下的所有资源。在加载资源过程中如果,在构造 PathMatchingResourcePatternResolver
实例的时候如果传入了 ClassLoader
,则调用其 getResources()
,否则调用ClassLoader.getSystemResources(path)
;ClassLoader.getResources()
如下
public Enumeration<URL> getResources(String name) throws IOException {
@SuppressWarnings("unchecked")
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
if (parent != null) {
tmp[0] = parent.getResources(name);
} else {
tmp[0] = getBootstrapResources(name);
}
tmp[1] = findResources(name);
return new CompoundEnumeration<>(tmp);
}
看到这里是不是就已经一目了然了?如果当前父类加载器不为 null
,则通过父类向上迭代获取资源,否则调用 getBootstrapResources()
。这里是不是特别熟悉,(▽)。
若 path
为 空(“”)时,则调用 addAllClassLoaderJarRoots()
方法。该方法主要是加载路径下得所有 jar
包,方法较长也没有什么实际意义就不贴出来了。
通过上面的分析,我们知道 findAllClassPathResources()
其实就是利用 ClassLoader
来加载指定路径下的资源,不管它是在 class
路径下还是在 jar
包中。如果我们传入的路径为空或者 /,则会调用 addAllClassLoaderJarRoots()
方法加载所有的 jar
包
当 locationPattern
以 classpath*:
开头且当中包含了通配符,则调用findAllClassPathResources()
方法进行资源加载
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
// 确定跟路径
String rootDirPath = determineRootDir(locationPattern);
String subPattern = locationPattern.substring(rootDirPath.length());
// 获取根据路径下得资源
Resource[] rootDirResources = getResources(rootDirPath);
Set<Resource> result = new LinkedHashSet<>(16);
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirUrl = rootDirResource.getURL();
// bundle 资源类型
if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
if (resolvedUrl != null) {
rootDirUrl = resolvedUrl;
}
rootDirResource = new UrlResource(rootDirUrl);
}
// VFS 资源
if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
}
// Jar
else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
}
else {
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[0]);
}
4. Resource
,ResourceLoader
和容器直接的微妙关系
Resource
,ResourceLoader
统一了资源和资源的定位,并且提供了Default
类提供了共有的默认实现;接着看它的继承关系
可以看到ApplicationContext
容器也继承了ResourceLoader
,所以任何的ApplicationContext
的实现也可以看作一个ResourceLoader
的实例
顺着下去可以看到AbstractApplicationContext
继承了DefaultResourceLoader
,并且有getResourcePatternResolver
方法把即子作为参数传入生成ResourcePatternResolver
实例(看前面讲的构造函数)
之后就可以调用ResourcePatternResolver
的getResources
方法获取相应的Resource
这也就是为什么高级容器支持统一资源加载的原因
5. ResourceLoader
的使用者BeanDefinitionReader
BeanDefinitionReader
就是使用ResourceLoader
将配置信息解析成BeanDefinition
的,并借助BeanDefinitonRegistry
注册器将BeanDefinition
注册到容器
AbstractBeanDefinitionReader
实现了BeanDefinitionReader
的公共处理逻辑,主要关注loadBeanDefinitions
方法的实现
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//获取资源加载器,主要的功能就是根据路径和类加载器获取Resource对象
ResourceLoader resourceLoader = getResourceLoader();
//判断类加载器是否为空
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//ResourcePatternResolver用于加载多个文件或者能够加载Ant风格路径的文件资源
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
//加载单个资源
//直接使用ResourceLoader加载
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
可以看到最终都会调用loadBeanDefinitions(resources);
进行资源的加载
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
counter += loadBeanDefinitions(resource);
}
return counter;
}
//BeanDefinitionReader
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
最终实际是交给BeanDefinitionReader
的子类去处理,根据不同的资源文件的类型,有不同的BeanDefinitionReader
实现类,主要关注对XM文件处理的XmlBeanDefinitionReader
,里面就会对loadBeanDefinitions
进行实现
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
//从本地线程变量获取当前正在加载的资源
//ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded 成员变量存储当前线程的EncodedResource集合
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
//如果本地线程变量中不存在正在加载的资源,那么将其添加进去
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
//如果EncodedResource添加进入currentResources失败,表示其中已经存在这个资源,只不过没有加载完成(不要多次加载同一个文件)
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//封装成InputSource,其中指定了输入流和编码格式
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//调用同类的方法进行解析
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
//doLoadBeanDefinitions
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//创建Document对象,XML文档对象,就是dom树
//使用这个Document对象可以获取XML文件的节点
//SAX XML
Document doc = doLoadDocument(inputSource, resource);
//解析dom树,并保存到BeanDefinition中,并向容器注册BeanDefinition
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
}
//registerBeanDefinitions
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//创建BeanDefinitionDocumentReader 这个是实际从XML的DOM树中读取BeanDefinition
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//获取注册表beanDefinitionMap在本此加载之前的BeanDefinition数量
int countBefore = getRegistry().getBeanDefinitionCount();
//加载并注册
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//本次加载的BeanDefinition数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
可以看到加载和注册时调用documentReader
的registerBeanDefinitions
方法实现
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//得到BeanDefinitionDocumentReader 完成对XML的BeanDefination的解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
//具体的解析过程在这里
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
registerBeanDefinitions
方法完成对Document
的解析实际上是在内部调用BeanDefinitionDocumentReader.registerBeanDefinitions()
方法完成对XML的解析,我们来看BeanDefinitionDocumentReader
的实现类DefaultBeanDefinitionDocumentReader
中registerBeanDefinitions
方法是怎么完成解析的
DefaultBeanDefinitionDocumentReader
根据Spring
定义的规则由BeanDefinitionParseDelegate
完成对Bean
进行解析完成对BeanDefination
的处理,处理结果由BeanDefinationHolder
对象持有,同时也持有Bean
的名字的等信息
BeanDefinitionParseDelegate
中定义了Spring Bean
定义的XML文件的各种元素
//根据Spring DTD对Bean的定义规则解析Bean定义Document对象
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
//获得XML描述符
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//获得Document的根元素
Element root = doc.getDocumentElement();
//具体的解析过程由BeanDefinitionParserDelegate实现,
//BeanDefinitionParserDelegate中定义了Spring Bean定义XML文件的各种元素
BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
//在解析Bean定义之前,进行自定义的解析,增强解析过程的可扩展性
preProcessXml(root);
//从Document的根元素开始进行Bean定义的Document对象
parseBeanDefinitions(root, delegate);
//在解析Bean定义之后,进行自定义的解析,增加解析过程的可扩展性
postProcessXml(root);
}
//创建BeanDefinitionParserDelegate,用于完成真正的解析过程
protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root) {
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
//BeanDefinitionParserDelegate初始化Document根元素
delegate.initDefaults(root);
return delegate;
}
//使用Spring的Bean规则从Document的根元素开始进行Bean定义的Document对象
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//Bean定义的Document对象使用了Spring默认的XML命名空间
if (delegate.isDefaultNamespace(root)) {
//获取Bean定义的Document对象根元素的所有子节点
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//获得Document节点是XML元素节点
if (node instanceof Element) {
Element ele = (Element) node;
//Bean定义的Document的元素节点使用的是Spring默认的XML命名空间
if (delegate.isDefaultNamespace(ele)) {
//使用Spring的Bean规则解析元素节点
parseDefaultElement(ele, delegate);
}
else {
//没有使用Spring默认的XML命名空间,则使用用户自定义的解//析规则解析元素节点
delegate.parseCustomElement(ele);
}
}
}
}
else {
//Document的根节点没有使用Spring默认的命名空间,则使用用户自定义的
//解析规则解析Document根节点
delegate.parseCustomElement(root);
}
}
//使用Spring的Bean规则解析Document元素节点
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//如果元素节点是<Import>导入元素,进行导入解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//如果元素节点是<Alias>别名元素,进行别名解析
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//元素节点既不是导入元素,也不是别名元素,即普通的<Bean>元素,
//按照Spring的Bean规则解析元素
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
}
//解析<Import>导入元素,从给定的导入路径加载Bean定义资源到Spring IoC容器中
protected void importBeanDefinitionResource(Element ele) {
//获取给定的导入元素的location属性
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
//如果导入元素的location属性值为空,则没有导入任何资源,直接返回
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
}
//使用系统变量值解析location属性值
location = SystemPropertyUtils.resolvePlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<Resource>(4);
//标识给定的导入元素的location是否是绝对路径
boolean absoluteLocation = false;
try {
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
}
catch (URISyntaxException ex) {
//给定的导入元素的location不是绝对路径
}
//给定的导入元素的location是绝对路径
if (absoluteLocation) {
try {
//使用资源读入器加载给定路径的Bean定义资源
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
}
else {
//给定的导入元素的location是相对路径
try {
int importCount;
//将给定导入元素的location封装为相对路径资源
Resource relativeResource = getReaderContext().getResource().createRelative(location);
//封装的相对路径资源存在
if (relativeResource.exists()) {
//使用资源读入器加载Bean定义资源
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
//封装的相对路径资源不存在
else {
//获取Spring IoC容器资源读入器的基本路径
String baseLocation = getReaderContext().getResource().getURL().toString();
//根据Spring IoC容器资源读入器的基本路径加载给定导入
//路径的资源
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
}
catch (IOException ex) {
getReaderContext().error("Failed to resolve current resource location", ele, ex);
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
ele, ex);
}
}
Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
//在解析完<Import>元素之后,发送容器导入其他资源处理完成事件
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
//解析<Alias>别名元素,为Bean向Spring IoC容器注册别名
protected void processAliasRegistration(Element ele) {
//获取<Alias>别名元素中name的属性值
String name = ele.getAttribute(NAME_ATTRIBUTE);
//获取<Alias>别名元素中alias的属性值
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
//<alias>别名元素的name属性值为空
if (!StringUtils.hasText(name)) {
getReaderContext().error("Name must not be empty", ele);
valid = false;
}
//<alias>别名元素的alias属性值为空
if (!StringUtils.hasText(alias)) {
getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
//向容器的资源读入器注册别名
getReaderContext().getRegistry().registerAlias(name, alias);
}
catch (Exception ex) {
getReaderContext().error("Failed to register alias '" + alias +
"' for bean with name '" + name + "'", ele, ex);
}
//在解析完<Alias>元素之后,发送容器别名处理完成事件
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}
//解析Bean定义资源Document对象的普通元素
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//对Document对象中<Bean>元素的解析由BeanDefinitionParserDelegate实现
//BeanDefinitionHolder是对BeanDefinition的封装,delegate解析完成后使用holder封装,bdHolder 包含了id ,别名和BeanDefinition的信息
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
//向Spring IoC容器注册解析得到的Bean定义,这是Bean定义向IoC容器注册的入口
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
//在完成向Spring IoC容器注册解析得到的Bean定义之后,发送注册事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
下面看一下parseBeanDefinitionElement()
方法的具体实现
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
//获取id的值
String id = ele.getAttribute(ID_ATTRIBUTE);
//获取name的值
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//分割name属性
List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
//将id赋值给beanName
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("....");
}
}
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
//该方法引发对Bean元素的详细解析
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
//程序执行到此处,整个<bean>标签的解析就算结束了。一个beanDefinition就创建好了
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
//如果不存在beanName,那么根据Spring中的提供的命名规则为当前bean生成对应的beanName
beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("....");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
//将信息封装到BeanDefinitionHolder中
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
BeanDefinition
可以看成是对<bean>
定义的抽象,这个数据对象中封装的数据大多都是与<bean>
定义相关的,也有很多就是我们在定义Bean时看到的那些Spring
标记。这个BeanDefinition
数据类型是非常重要的,它封装了很多基本数据,这些都是Ioc容器需要的,上面代码最后我们返回了一个BeanDefinitionHolder
实例,这个实例封装了beanDefinition,beanName, aliase
三个信息,beanDefinition
中也包含了beanName,aliase
信息
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
//这里只读取定义的<bean>中设置的class名字,然后载入到BeanDefinition中去,只是做个记录,并不涉及对象的实例化过程,对象的实例化过程实际是在依赖注入时完成的
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
//解析parent属性
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
//创建用于承载属性的AbstractBeanDefinition类型的GenereicBeanDefinition
//这里生成需要的BeanDefinition对象,为Bean定义的信息做载入做准备
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//这里对当前Bean元素进行属性解析,并设置description信息
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//解析元数据
parseMetaElements(ele, bd);
//解析lookup-method属性
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//解析replaced-method属性
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析构造函数参数
parseConstructorArgElements(ele, bd);
//解析property子元素
parsePropertyElements(ele, bd);
//解析qualifier子元素
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
//返回BeanDefinition对象
return bd;
}
catch (ClassNotFoundException ex) {
...
}
catch (NoClassDefFoundError err) {
...
}
catch (Throwable ex) {
...
}
finally {
this.parseState.pop();
}
return null;
}
从上面的代码可以看出,要解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition
类型的实例,而代码createBeanDefinition(className,parent)
的作用就是实现此功能。创建完承接的实例后,便可以进行各种属性的解析了,首先进行解析的是在<bean></bean>
标签中定义的各种属性,如scope, singleton,abstract,lazy-init
等,然后再解析子标签中的属性,如:lookup-method ,constructor-arg
等。解析完成之后关联到实例上,之所以能进行关联,是因为xml
中所有的配置都能在GenericBeanDefinition
的实力类中找到对应的配置。 此时容器还没有起作用,要想起作用,需要向容器进行注册
接下来回到processBeanDefinition
方法
//解析Bean定义资源Document对象的普通元素
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//对Document对象中<Bean>元素的解析由BeanDefinitionParserDelegate实现
//BeanDefinitionHolder是对BeanDefinition的封装,delegate解析完成后使用holder封装,bdHolder 包含了id ,别名和BeanDefinition的信息
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
//向Spring IoC容器注册解析得到的Bean定义,这是Bean定义向IoC容器注册的入口
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
//在完成向Spring IoC容器注册解析得到的Bean定义之后,发送注册事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
分析到这,已经完成了xml
文件向BeanDefinition
的转化,每个一个<bean>
标签都会转化成一个BeanDefinition
对应的实体。实体中包含了<bean>
中定义的所有属性,接下来就要调用BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
向容器中注册BeanDefinition
在DefaultListableBeanFactory
中是通过一个ConcurrentHashMap
来持有载入的BeanDefinition
的
//存储注册的BeanDefinition
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
//向IoC容器注册解析的BeanDefiniton
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
//注册前的最后一次校验
//主要是对AbstractBeanDefinition属性中的methodOverrides校验
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
....
}
}
BeanDefinition oldBeanDefinition;
//检查是否有同名的beanName存在,判断allowBeanDefinitionOveriding属性
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw ...
}
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("....");
}
}
else if (!beanDefinition.equals(oldBeanDefinition)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("....");
}
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("...");
}
}
//如果允许覆盖则执行覆盖操作
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
//判断是否已经有其他的Bean开始初始化
//在容器启动的最后会预初始化所有的singleton beans
if (hasBeanCreationStarted()) {
//注册的过程中需要线程同步,以保证数据的一致性
synchronized (this.beanDefinitionMap) {
//注册
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
//正常情况下应该走此分支
//将BeanDefinition放入Map中,此map中保存了所有的BeanDefinition
this.beanDefinitionMap.put(beanName, beanDefinition);
//这个ArrayList中会按照bean配置的顺序保存每一个注册的Bean的名字
this.beanDefinitionNames.add(beanName);
//手动注册singleton bean
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
至此,Bean
定义资源文件中配置的Bean
被解析过后,已经注册到IoC
容器中,被容器管理起来,真正完成了IoC
容器初始化所做的全部工作。现 在IoC
容器中已经建立了整个Bean
的配置信息,这些BeanDefinition
信息已经可以使用,并且可以被检索,IoC
容器的作用就是对这些注册的Bean
定义信息进行处理和维护。这些的注册的Bean定义信息是IoC
容器控制反转的基础,正是有了这些注册的数据,容器才可以进行依赖注入
7. 总结
-
Spring
提供了Resource
和ResourceLoader
来统一抽象整个资源及其定位。使得资源与资源的定位有了一个更加清晰的界限,并且提供了合适的Default
类,使得自定义实现更加方便和清晰。 -
AbstractResource
为Resource
的默认实现,它对Resource
接口做了一个统一的实现,子类继承该类后只需要覆盖相应的方法即可,同时对于自定义的Resource
我们也是继承该类。 -
DefaultResourceLoader
同样也是ResourceLoader
的默认实现,在自定ResourceLoader
的时候我们除了可以继承该类外还可以实现ProtocolResolver
接口来实现自定资源加载协议 -
DefaultResourceLoader
每次只能返回单一的资源,所以Spring
针对这个提供了另外一个接口ResourcePatternResolver
,该接口提供了根据指定的locationPattern
返回多个资源的策略。其子类PathMatchingResourcePatternResolver
是一个集大成者的ResourceLoader
,因为它即实现了Resource getResource(String location)
也实现了Resource[] getResources(String locationPattern)
-
ApplicationContext
承了ResourceLoader
,任何的ApplicationContext
的实现也可以看作一个ResourceLoader
的实例,其中组合了ResourcePatternResolver
成员变量,这也是高级容器可以实现统一资源定位的关键
最后
以上就是雪白短靴为你收集整理的Spring Framework的核心 - 统一资源加载策略的全部内容,希望文章能够帮你解决Spring Framework的核心 - 统一资源加载策略所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复