Spring解析Xml注册Bean流程

有道无术,术可求;

有术无道,止于术;

读源码是一个很枯燥的过程,但是Spring源码里面有很多值得学习的地方

加油~!!!!!

前言

使用SpringMVC的时候,通常使用下面这行代码来加载Spring的配置文件

ApplicationContext application = new ClassPathXmlApplicationContext("webmvc.xml"),那么这行代码到底进行了怎么的操作,接下来就一探究境,看看是如何加载配置文件的

Spring的配置文件

这个配置文件对于已经学会使用SpringMVC的你来说已经再熟悉不过了

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    <bean id="study" class="com.xiaobai.Student">
    </bean>
</beans>

那么Spring是如何进行Bean的注册的呢?经过这几天的源码查看我写下了这篇文章来作为笔记,

因为我刚开始看Spring的源码,里面有些内容可能理解的不是很到位,有错误请指出

源码查看

再此之前我先bb几句,为了方便查看源码,可以去GitHub上下载Spring的源码导入到Idea或者是eclipse中这样查看起来更方便些,同时还可以在上面写一些注释

既然使用的是ClassPathXmlApplicationContext("webmvc.xml")那就找到这个类的单参构造器查看跟踪下源码

/**
	 * Create a new ClassPathXmlApplicationContext, loading the definitions
	 * from the given XML file and automatically refreshing the context.
	 * @param configLocation resource location
	 * @throws BeansException if context creation failed
	 * 这个是创建 了 一个 ClassPathXmlApplicationContext,用来从给的XMl文件中加载规定
	 */
	public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}

这里调用的是本类中的另外一个三个参数的构造方法,便进入到了下面这些代码中

/**
	 * Create a new ClassPathXmlApplicationContext with the given parent,
	 * loading the definitions from the given XML files.
	 * @param configLocations array of resource locations
	 * @param refresh whether to automatically refresh the context,
	 * loading all bean definitions and creating all singletons.
	 * Alternatively, call refresh manually after further configuring the context.
	 * @param parent the parent context
	 * @throws BeansException if context creation failed
	 * @see #refresh()
	 */
	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		super(parent);
    	//设置配置文件的路径
		setConfigLocations(configLocations);
		if (refresh) {
      	   //重要的方法,需要进入查看
			refresh();
		}
	}

这里来说下这个方法的参数的意思:

  • configLocations:这个里面保存的是配置文件的路径
  • Refresh:是否自动刷新上下文
  • parent:父上下文

设置资源加载器

要跟踪下super(parent)这行代码,在它的父类中(AbstractApplicationContext类里面),有下面的代码,这段代码的作 用是获取一个SpringResource的加载器用来加载资源文件(这里你可以理解为是为了加载webmvc.xml配置文件做前期的准备)

protected ResourcePatternResolver getResourcePatternResolver() {
	return new PathMatchingResourcePatternResolver(this);
}
//下面的方法在PathMatchingResourcePatternResolver类中,为了查看方便我将这两个方法写在了一起
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
	Assert.notNull(resourceLoader, "ResourceLoader must not be null");
	this.resourceLoader = resourceLoader;
}

在PathMatchingResourcePatternResolver构造方法中就设置了一个资源加载器

设置Bean信息位置

这个里面有一个setConfigLocations方法,这个里面会设置Bean配置信息的位置,这个方法的所在的类是AbstractRefreshableConfigApplicationContext,它和CLassPathXmlApplicationContext之间是继承的关系

@Nullable
private String[] configLocations;
public void setConfigLocations(@Nullable 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++) {
				this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
	}

这里面的configLocations的是一个数组,setConfigLocations方法的参数是一个可变参数,这个方法的作用是将多个路径放到configLocations数组中

阅读refresh

这个方法可以说是一个非常重要的一个方法,这在个方法里面规定了容器的启动流程,具体的逻辑通过ConfigurableApplicationContext接口的子类实现

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
      	   //进入到此方法查看
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				//这里面的代码我删除掉了,因为我们本文是看的解析XML创建 Bean的文章,这里的代码暂时用不到,我就删除了,要不然代码太多了
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset ‘active‘ flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring‘s core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

Bean的配置文件是在这个方法里面的refreshBeanFactory方法来处理的,这个方法是在AbstractRefreshableApplicationContext类中实现的

@Override
protected final void refreshBeanFactory() throws BeansException {
  if (hasBeanFactory()) {
    destroyBeans();
    closeBeanFactory();
  }
  try {
    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);
  }
}

这里有一个方法是loadBeanDefinitions(beanFactory)在这个方法里面就开始解析配置文件了,进入这个方法

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
  // Create a new XmlBeanDefinitionReader for the given BeanFactory.
  XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

  // Configure the bean definition reader with this context‘s
  // resource loading environment.
  beanDefinitionReader.setEnvironment(this.getEnvironment());
  beanDefinitionReader.setResourceLoader(this);
  beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

  // Allow a subclass to provide custom initialization of the reader,
  // then proceed with actually loading the bean definitions.
  initBeanDefinitionReader(beanDefinitionReader);
  //Bean读取器实现加载的方法
  loadBeanDefinitions(beanDefinitionReader);
}

进入到loadBeanDefinitions(XmlBeanDefinitionReader reader)方法

XML Bean读取器加载Bean配置资源

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
 //获娶Bean配置资源的位置
  Resource[] configResources = getConfigResources();
  if (configResources != null) {
    reader.loadBeanDefinitions(configResources);
  }
  String[] configLocations = getConfigLocations();
  if (configLocations != null) {
    reader.loadBeanDefinitions(configLocations);
  }
}

但是本文的教程是通过ClassPathXmlApplicationContext来举的例子,getConfigResources()方法返回的是空的,就执行下面的分支

说点和本文有关也有可能没有关系的话

当代码看到这里,学习过设计模式的同鞋可能会发现我们看过的这些代码里也涉及到了委派模式策略模式因为Spring框架中使用到了很多的设计模式,所以说在看一些框架源码的时候,我们尽可能的先学习下设计模式,不管是对于看源码来说或者是对于在公司中工作都是启到了很重要的作用,在工作中使用了设计模式对于以后系统的扩展或者是维护来说都是比较方便的。当然学习设计模式也是没有那么的简单,或许你看了关于设计模式的视频或者是一些书籍,但是在工作中如果是想很好的运用出来,还是要写很多的代码和常用设计模式的。

学习设计模式也是投入精力的,Scott Mayer在《Effective C++》也说过:C++新手和老手的区别就是前者手背上有很多的伤疤。

未完....

相关推荐