Spring ioc的资源定位 载入和注册
以FileSystemXmlApplicationContext为例
package org.springframework.context.support;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
public FileSystemXmlApplicationContext() {
}
public FileSystemXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[]{configLocation}, true, (ApplicationContext)null);
}
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, (ApplicationContext)null);
}
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, (ApplicationContext)null);
}
--核心的构造方法基本其他的构造方法都是通过调用此方法进行的--
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if(refresh) {
this.refresh();
}
}
protected Resource getResourceByPath(String path) {
if(path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
}
在核心的构造函数中发现调用了
- super(parent)
- this.setConfigLocations(configLocations);
- this.refresh()
理解为调用父类容器的构造方法(super(parent)方法)为容器设置好Bean资源加载器。
然后,再调用父类AbstractRefreshableConfigApplicationContext的setConfigLocations(configLocations)方法设置Bean定义资源文件的定位路径。
通过追踪FileSystemXmlApplicationContext的继承体系。
1. ### 先看super(parent)
首先调用了父类AbstractXmlApplicationContext的构造
public AbstractXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
继续
public AbstractRefreshableConfigApplicationContext(ApplicationContext parent) {
super(parent);
}
再继续
public AbstractApplicationContext(ApplicationContext parent) {
this();
this.setParent(parent);
}
this();的具体构造如下.
public AbstractApplicationContext() {
this.logger = LogFactory.getLog(this.getClass());
this.id = ObjectUtils.identityToString(this);
this.displayName = ObjectUtils.identityToString(this);
this.beanFactoryPostProcessors = new ArrayList();
this.active = new AtomicBoolean();
this.closed = new AtomicBoolean();
this.startupShutdownMonitor = new Object();
this.applicationListeners = new LinkedHashSet();
this.resourcePatternResolver = this.getResourcePatternResolver();
}
从this.resourcePatternResolver =this.getResourcePatternResolver();跟进去会发现
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
//设置Spring的资源加载器
this.resourceLoader = resourceLoader;
}
FileSystemXmlApplicationContext的继承体系如下:
- 由此继承体系可知道FileSystemXmlApplication的基类接口就是一个ResourceLoader而实现这个接口的类是DefautResourceLoader,所以默认的资源加载器会是DefautResourceLoader
- 由源码发现FileSystemXmlApplication通过调用父类AbstractXmlApplicationContext构造方法,该类又通过调用其父类AbstractRefreshableConfigApplicationContext的构造方法,AbstractRefreshableConfigApplicationContext类继续调用父类直到AbstractApplicationContext类后调用其默认构造方法,通过该类的getResourcePatternResolver()return new PathMatchingResourcePatternResolver返回一个ResourcePatternResolver实例有源码
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String var1) throws IOException;
}
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {.....}
由源码知道 ResourcePatternResolver、AbstractApplicationContext都继承了 ResourceLoader, PathMatchingResourcePatternResolver实现ResourcePatternResolver 所以AbstractApplicationContext、PathMatchingResourcePatternResolver都从而实现了spring中ResourceLoader的设置,这里也就仅仅是设置而已。
2. ## 再看this.setConfigLocations(configLocations)
/即多个资源文件路径之间用” ,; /t/n”分隔,解析成数组形式
public void setConfigLocation(String location) {
this.setConfigLocations(StringUtils.tokenizeToStringArray(location, ",; \t\n"));
}
//解析Bean定义资源文件的路径,处理多个资源文件字符串数组
public void setConfigLocations(String... locations) {
if(locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for(int i = 0; i < locations.length; ++i) {
// resolvePath为同一个类中将字符串解析为路径的方法 实际就是字符串
this.configLocations[i] = this.resolvePath(locations[i]).trim();
}
} else {
this.configLocations = null;
}
}
protected String resolvePath(String path) {
return this.getEnvironment().resolveRequiredPlaceholders(path);
}
至此,Spring IoC容器在初始化时将配置的Bean定义资源文件定位为Spring封装的Resource。
- ## 最后 this.refresh()
refresh()在基类AbstractApplicationContext中
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
//告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从子类的refreshBeanFactory()方法启动
采用委派的方式即在类中只有一个abstract方法由子类实现。
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
<---这些方法先不管-->
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if(this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
Spring IoC容器载入Bean定义资源文件从其子类容器的refreshBeanFactory()方法启动,所以整个refresh()中
ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();
这句以后代码的都是注册容器的信息源和生命周期事件,载入过程就是从这句代码启动。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//这里使用了委派设计模式,父类定义了抽象的refreshBeanFactory()方法
,具体实现调用子类容器的refreshBeanFactory()方法
this.refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
if(this.logger.isDebugEnabled()) {
this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
子类AbstractRefreshableApplicationContext实现refreshBeanFactory()
protected final void refreshBeanFactory() throws BeansException {
//如果已经有容器,销毁容器中的bean,关闭容器
if(this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
try {
<!--创建ioc容器--->
DefaultListableBeanFactory ex = this.createBeanFactory();
ex.setSerializationId(this.getId());
<!--对容器进行制定只要是true flase的设置-->
this.customizeBeanFactory(ex);
//调用载入Bean定义的方法,主要这里又使用了一个委派模式,在当前
类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器
this.loadBeanDefinitions(ex);
Object var2 = this.beanFactoryMonitor;
synchronized(this.beanFactoryMonitor) {
this.beanFactory = ex;
}
看子类AbstractXmlApplicationContext中的loadBeanDefinitions方法
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
<!--创建bean资源读取器-->
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
<!--对reader进行一系列的设置
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
<!加载的地方
this.loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
/获取Bean定义资源的定位 //这里又使用了一个委托模式,调用子类的获取Bean定义资源定位的方法
//该方法在ClassPathXmlApplicationContext中进行实现,对于我们
//举例分析源码的FileSystemXmlApplicationContext没有使用该方法
Resource[] configResources = this.getConfigResources();
if(configResources != null) {
//Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位
//的Bean定义资源
reader.loadBeanDefinitions(configResources);
}
如果子类中获取的Bean定义资源定位为空,则获取FileSystemXmlApplicationContext构造方法中setConfigLocations方法设置的资源
String[] configLocations = this.getConfigLocations();
if(configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
不是很到位的理解一下:
首先从FileSystemXmlApplicationContext这个类的refresh()启动,基类AbstractApplicationContext实现的在这个方法里同时调用了obtainFreshBeanFactory()而在
obtainFreshBeanFactory()里通过调用refreshBeanFactory()创建IoC容器、,并调用loadBeanDefinitions(beanFactory)装载bean,这两个方法都是通过委派实现的。
loadBeanDefinitions(beanFactory)中通过调用loadBeanDefinitions(beanDefinitionReader)实现bean加载,在loadBeanDefinitions(beanDefinitionReader)里Bean
读取器(reader)调用其父类AbstractBeanDefinitionReader读取定位的Bean定义资(这里用了两种方式一个是直接的url定位读取,另一种是在resource里面读取)
这里委派调用其子类XmlBeanDefinitionReader 的方法loadBeanDefinitions(resource),实现加载功能。在loadBeanDefinitions(resource)中调
用loadBeanDefinitions(EncodedResource encodedResource)载入XML形式Bean定义资源文件具体读取过程的方法doLoadBeanDefinitions(),在该
方法中调用documentLoader.loadDocument(..)把XML文件转换为DOc对象接着调用registerBeanDefinitions(Document doc, Resource resource)
按照Spring的Bean语义要求将Bean定义资源解析并转换为容器内部数据结构,该方法里面调用了registerBeanDefinitions(Document doc, XmlReaderContext
readerContext)委派子类--根据
Spring DTD对Bean的定义规则解析Bean定义Document对象里面调用很多方法解析xml文件中所用标签例如:
</bean>等。最后BeanDefinitionReaderUtils.registerBeanDefinition进入注册,当调用BeanDefinitionReaderUtils向IoC容器注册解析的BeanDefinition时,真正
完成注册功能的是DefaultListableBeanFactory.