Spring的爬坑之路(二)ClassPathXmlApplicationContext
首先呢,我们通过debug跟踪代码的方式,首先去了解一下我们常用到的一些逻辑情况,spring逻辑足够庞大,对于第一次阅读甚至是第一次尝试阅读源码的我来说,这也是我想到的一个好的方法…
AppTest
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Spring.xml");
ClassPathXmlApplicationContext
- ClassPathXmlApplicationContext的构造方法
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent); // null
this.setConfigLocations(configLocations); // spring.xml
if (refresh) { // true
this.refresh();
}
}
方法中主要有三个步骤:
1- 初始化属性信息
2- setConfigLocations主要是加载Spring配置文件的位置
3- 更新beanFactory
一、super(parent)
- AbstractApplicationContext的构造。
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();
}
- AbstractApplicationContext.setParent方法
public void setParent(ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
this.getEnvironment().merge((ConfigurableEnvironment)parentEnvironment);
}
}
}
二、 setConfigLocations
/**
* spring 原文注视:
* Set the config locations for this application context.
* <p>If not set, the implementation may use a default as appropriate.
*/
public void setConfigLocations(@Nullable String... locations) { // spring.xml
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++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
三、 refresh函数
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备这个上下文以进行刷新。
prepareRefresh();
// 告诉子类刷新内部bean工厂。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 准备在此上下文中使用的bean工厂。
prepareBeanFactory(beanFactory);
try {
// 允许在上下文子类中对bean工厂进行后处理。
postProcessBeanFactory(beanFactory);
// 调用上下文中注册为bean的工厂处理器。
invokeBeanFactoryPostProcessors(beanFactory);
// 注册拦截bean创建的bean处理器。
registerBeanPostProcessors(beanFactory);
// 为此上下文初始化消息源。
initMessageSource();
// 为此上下文初始化事件多播器。
initApplicationEventMulticaster();
// 初始化特定上下文子类中的其他特殊bean
onRefresh();
// 检查侦听器bean并注册它们。
registerListeners();
// 实例化所有剩余的(非延迟-init)单例。
finishBeanFactoryInitialization(beanFactory);
// 最后一步:发布相应的事件。
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 销毁已经创建的单例,以避免悬空资源。
destroyBeans();
// 重置“活跃”的标示。
cancelRefresh(ex);
// 将异常传播给调用者
throw ex;
} finally {
// 重置Spring核心中的普通自省缓存,因为我们
// 可能再也不需要单例bean的元数据了……
resetCommonCaches();
}
}
}
- 在此方法内,我将每一步的主要功能进行了注视,总体流程也比较清晰。核心就是obtainFreshBeanFactory方法来创建新的BeanFactory。
详细看一下 obtainFreshBeanFactory方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
AbstractRefreshableApplicationContext.refreshBeanFactory()
/**
* This implementation performs an actual refresh of this context's underlying
* bean factory, shutting down the previous bean factory (if any) and
* initializing a fresh bean factory for the next phase of the context's lifecycle.
* 此实现执行上下文底层的实际刷新
* bean工厂,关闭前一个bean工厂(如果有的话)和
* 为上下文生命周期的下一阶段初始化一个新鲜的bean工厂。
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 创建新的beanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
- 终于到关键地方了 ,主要逻辑:
1、 在此方法内首先回进行销毁bean 并清理BeanFactory
2、然后创建新的BeanFactory加载beans
AbstractXmlApplicationContext.loadBeanDefinitions()
/**
* Load the bean definitions with the given XmlBeanDefinitionReader.
* <p>The lifecycle of the bean factory is handled by the {@link #refreshBeanFactory}
* method; hence this method is just supposed to load and/or register bean definitions.
*
* 使用给定的XmlBeanDefinitionReader加载bean定义。
* bean工厂的生命周期由{@link #refreshBeanFactory}处理
* 方法;因此,该方法仅用于加载和/或注册bean定义。
*
* @param reader the XmlBeanDefinitionReader to use
* @throws BeansException in case of bean registration errors
* @throws IOException if the required XML document isn't found
* @see #refreshBeanFactory
* @see #getConfigLocations
* @see #getResources
* @see #getResourcePatternResolver
*/
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);
}
}
loadBeanDefinitions函数执行时序图
- 从上面的图中我们尝试梳理整个的处理过程:
- 封装资源文件 。当进入 XmlBeanDefinitionReader 后首先对参数 Resource 使用 EncodedResource类进行封装。
- 获取输入流 。 从 Resource 巾获取对应的 InputStrearn 并构造 lnputSource。
- 通过构造的 lnputSource 实例和 Resource 实例继续调用函数 doLoadBeanDefinitions。
我们来看一下 loadBeanDefinitions 函数具体的实现过程 。
XmlBeanDefinitionReader.loadBeanDefinitions()
/**
* Load bean definitions from the specified XML file.
* @param encodedResource the resource descriptor for the XML file,
* allowing to specify an encoding to use for parsing the file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
*/
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(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 = 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();
}
}
}
- 整理数据准备阶段的逻辑,首先对传入的 resource 参数做封装,目的是考虑到
Resource 可能存在编码要求的情况,其次,通过 SAX 读取 XML 文件的方式来准备 lnputSource
对象,最后将准备的数据通过参数传入真正 的核心处理部分 doLoadBeanDefinitions(inputSource, encodedResource.getResource())。
XmlBeanDefinitionReader.doLoadBeanDefinitions()
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
- 在此方法内,抛除异常处理之外, 其实只做了三件事,这三件事的每一件都 必不可少 。
1、 获取对 XML 文件的验证模式 。
2、加载 XML 文件,并得到对应的 Document。
3、根据返回的 Document 注册 Bean 信息 。
到这三步骤,逻辑非常复杂,特别是第三部对配置文件的解析,一时半会也实在是研究不透… 我们第一篇文稿先到这儿,接下来还是要消化一下…