我们大体上知道spring可以通过读取xml配置文件,创建创建对象,然后放到ioc容器中,哪在代码层面是如何实现?这个过程用了什么的样的设计思想?本篇主要主要从整体结构上阅读spring的源码,看是如何进行资源定位?
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;
}
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
定义资源加载器,主要应用于根据给定的资源文件地址,返回对应的 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
中实现
org.springframework.core.io.DefaultResourceLoader
是 ResourceLoader
的默认实现。其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);
}
}
}
另外需要关注的是:
ResourceLoader
的 Resource 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
最常用的子类,它除了支持 ResourceLoader
和 ResourcePatternResolver
新增的 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是如何使用Resource
跟ResourceLoader
的
创建一个测试主函数
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());
}
这里先暂且忽略其他流程,一致找到加载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.
ClassPathXmlApplicationContext
的getResource
最终又委托给了ResourcePatternResolver
进行实现
ResourcePatternResolver
再根据不同的location
匹配返回不同的Resource
对象
Resource
对象ResourceLoader
进行加载,返回不同的Resouce
对象ResourcePatternResolver
对ResourceLoader
进行了增强,可以处理多个locationClassPathXmlApplicationContext
也实现了ResourceLoader
,但并不直接获取Resource
,而是委托给了ResourcePatternResolver
进行处理到这里,还有一点没有弄清楚,ClassPathXmlApplicationContext
为什么要实现ResourceLoader
呢?