目录
一、总览
在使用IOC容器之前,需要定义一个Resource来定位容器BeanDefinition的资源文件,Resource类继承关系如图1所示,参考使用XmlBeanFactory 和DefaultListableBeanFactory两个IOC容器时,均使用了ClassPathRescource作为BeanDefinition数据源,如下所示:
ClassPathResource resource = new ClassPathResource("beans.xml");
![](https://i-blog.csdnimg.cn/blog_migrate/cf8a13d013379ec39fbde592d6bfc2f4.png)
我们常用的ApplicationContext容器为我们提供了一系列加载不同Resource的功能,比如FileSystemApplicationContext、ClassPathXmlApplicationContext、XmlWebApplicationContext等,下面我们以FileSytemXmlApplicationContext为例,看一看ApplicationContext的Resource定位过程。
FileSystemApplicationContext的类继承关系图请参考Spring源码学习【一】初识IOC容器
其继承自AbstractXmlApplicationContext,IOC容器的功能由其父类实现,其主要是扩展了从文件系统读取BeanDefinition配置文件的功能,体现在覆盖了DefaultResourceLoader的getResourceByPath方法(参考 Spring源码学习【一】初识IOC容器)。
在FileSystemXmlApplicationContext的构造方法中,调用了refresh()方法来启动IOC容器的初始化,这是整个IOC容器初始化的入口,具体的调用的过程如图2所示,下面从源码的角度对这个过程进行分析。
![](https://i-blog.csdnimg.cn/blog_migrate/4754ebb81ccbb73b76ff70019aca5d90.png)
二、源码分析
1. refresh
refresh()方法在FileSystemXmlApplicationContext的构造方法中调用,这一方法定义在AbstractApplicationContext中,启动了IOC容器的初始化过程,如下所示:
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
/**
* 创建一个应用上下文,根据应用环境解析路径,并启动refresh()过程
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
}
2. obtainFreshBeanFactory
obtainFreshBeanFactory用于通知子类刷新内部的bean factory,其中调用了refreshBeanFactory方法,这一方法在AbstractApplicationContext中未给出具体实现,留给其子类实现,最终调用的为其子类AbstractRefreshableApplicationContext的refreshBeanFactory方法,代码如下:
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements Configu-rableApplicationContext {
……
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
……
// 通知子类刷新内部的bean factory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
……
}
}
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
}
3. refreshBeanFactory
refreshBeanFactory由AbstractRefreshableApplicationContext类实现,且声明为final方法,不可以被覆盖。在这个方法中,首先判断如果已经创建了beanFactory,则销毁bean并关闭beanFactory,然后创建一个新的DefaultListableBeanFactory作为应用上下文的IOC容器并由当前类对象持有这个beanFactory,同时调用loadBeanDefinitions方法载入BeanDefinition。这里的loadBeanDefinitions是一个抽象方法,留给其子类实现。
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext{
……
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) { // 若已创建则销毁bean关闭beanFactory
destroyBeans();
closeBeanFactory();
}
// 创建一个新的DefaultListableBeanFactory作为应用上下文的IOC容器
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 载入BeanDefinition
loadBeanDefinitions(beanFactory);
// 由当前类对象持有这个beanFactory
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException;
}
4-5. loadBeanDefinitions
loadBeanDefinitions由AbstractXmlApplicationContext实现,在这个方法中,首先创建了一个XmlBeanDefinitionReader对象,并将这个reader回调给由父类创建的DefaultListableBeanFactory对象,然后对reader进行了一系列配置,最后调用了reader的loadBeanDefinitions方法,代码如下所示:
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplication-Context {
……
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 为给定的beanFactory创建一个XmlBeanDefnitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 用当前上下文的资源加载环境配置reader
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 初始化reader
initBeanDefinitionReader(beanDefinitionReader);
// 启动beanDefinition的加载
loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
}
注:
beanDefinitionReader.setResourceLoader(this);
将这个ApplicationContext对象作为了reader的ResourceLoader,根据之前的分析我们能够知道,AbstractXmlApplicationContext间接继承了DefaultResourceLoader,而DefaultResourceLoader又实现了ResourceLoader接口,所以整个继承关系中的ApplicationContext类均为ResourceLoader类型的实例,在最后调用resourceLoader的getResource方法时实则调用了ApplicationContext的getResourceByPath方法,这就是为什么FileSystemXmlApplicationContext重写了getResourceByPath方法就可以实现从文件系统读取XML格式的配置文件。
6. loadBeanDefinitions
这一步调用了XmlBeanDefinitionReader父类AbstractBeanDefinitionReader的loadBeanDefinitions方法,这里有一系列loadBeanDefinitions方法的重载,在最终的调用方法中我们可以看到,对resourceLoader的类型进行了判断,如果resourceLoader是ResourcePatternResolver类型的实例则以通配符模式定义的路径定位资源,否则直接通过路径定位资源。
这里的resourceLoader正是上一步为reader设置的ApplicationContext实例,如图3所示
![](https://i-blog.csdnimg.cn/blog_migrate/8067b0909db35287acf30a0fff1bf134.png)
AbstractAppliactionContext实现了ResourcePatternResolver接口,作为ResourcePatternResolver类型的resourceLoader实例对象使用,并调用了getResources方法来获得Resource资源对象,代码如下所示:
public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {
……
@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;
}
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// 通配符模式匹配方式
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 {
// 通过绝对路径获取单个资源
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;
}
}
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}
}
这里调用的getResources方法是定义在ResourcePatternResolver接口中的方法,具体由PathMatchingResourcePatternResolver类实现,在AbstractApplicationContext中持有PathMatchingResourcePatternResolver实例,因此这一过程中调用的getResources实际为PathMatchingResourcePatternResolver对象的getResources方法。
7. getResources
getResources方法由PathMatchingResourcePatternResolver实现,代码如下:
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
……
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
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)};
}
}
}
}
8. getResource
最终调用了resourceLoader的getResource方法,并在其中调用了getResourceByPath方法。如下为DefaultResourceLoader的实现,但在FileSystemXmlApplicationContext的实现中,实际上调用了被覆盖的getResourceByPath方法,从而实现了从文件系统读取资源文件的功能。
public class DefaultResourceLoader implements ResourceLoader {
……
@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);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// 尝试将路径解析为URL路径
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlRe-source(url));
}
catch (MalformedURLException ex) {
// 非URL路径,以普通路径形式解析
return getResourceByPath(location);
}
}
}
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
}
至此,FileSystemXmlApplicationContext就完成了FileSystemResource的定位工作,有了这个Resource,下一步就可以进行BeanDefinition的载入和注册过程了。