Spring源码01---容器刷新前配置

容器刷新前配置: https://www.cnblogs.com/xiaomaomao/p/14046219.html

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		// The application context id is still set to its original default value
		// -> assign a more useful id based on available information
		String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
		if (idParam != null) {
			wac.setId(idParam);
		}
		else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(sc.getContextPath()));
		}
	}

	wac.setServletContext(sc);
	String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
	if (configLocationParam != null) {
		wac.setConfigLocation(configLocationParam);
	}

	// The wac environment's #initPropertySources will be called in any case when the context
	// is refreshed; do it eagerly here to ensure servlet property sources are in place for
	// use in any post-processing or initialization that occurs below prior to #refresh
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
	}

	customizeContext(sc, wac);
	// 调用 AbstractApplicationContext 的 refresh 方法
	wac.refresh();
}

wac 是 XmlWebApplicationContext 类型的, XmlWebApplicationContext 的继承结构如下:

 Spring源码01---容器刷新前配置

这里的 wac 是 XmlWebApplicationContext 类型的,在它本类中没有找到 refresh() 方法,那么就往上找,结果在这张图的最上层接口 ConfigurableApplicationContext 中找到了 refresh() 方法,而ConfigurableApplicationContext 接口的实现类中只有 AbstractApplicationContext 这个抽象类实现了 refresh() 方法,所以我们最终会去到 AbstractApplicationContext 类中的 refresh()

找到 AbstractApplicationContext 类中的 refresh() 方法,我们主要看一下这个方法到底做了什么?

public void refresh() throws BeansException, IllegalStateException {
	// 同步锁,如果 refresh() 方法还没有执行完成,这个时候你突然继续再来一次容器启动或者容器销毁的动作,那么就乱套了
	synchronized (this.startupShutdownMonitor) {

		prepareRefresh();

		// 告知子类刷新内部的 Bean Factory
		// 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,并注册到 BeanFactory 中.
        // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
        // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
	}
	......
}

整个 refresh() 方法是 Spring IOC 的核心方法,可以看到 refresh 里面有很多的方法,但是我们这里探究的是刷新 BeanFactory 的操作,看看刷新 BeanFactory 的时候到底执行了哪一些的操作 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()

首先我们来到 obtainFreshBeanFactory() 方法

代码块一、obtainFreshBeanFactory()

// AbstractApplicationContext 类中的方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	// 1、刷新 BeanFactory,由 AbstractRefreshableApplicationContext 实现----(详细见代码块二)
	refreshBeanFactory();
	// 2、获取刚刚创建的 BeanFactory
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if (logger.isDebugEnabled()) {
		logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
	}
	// 3、返回 BeanFactory
	return beanFactory;
}

代码块二、refreshBeanFactory()

// AbstractRefreshableApplicationContext 类中的方法
protected final void refreshBeanFactory() throws BeansException {
	// 如果 ApplicationContext 中已经加载过了 BeanFactory ,销毁所有 Bean ,关闭 BeanFactory
	// 注意:应用中 BeanFactory 本来就是可以多个的,这里可不是说应用全局是否有 BeanFactory,而是当前的
	// ApplicationContext 是否持有 BeanFactory
	if (hasBeanFactory()) {
		destroyBeans();
		closeBeanFactory();
	}
	try {
		// 2、创建 BeanFactory ,为什么那么多的 IOC 容器,单单要创建 DefaultListableBeanFactory 呢?这个下面会说到
		DefaultListableBeanFactory beanFactory = createBeanFactory();
		// 3、设置序列化 ID
		beanFactory.setSerializationId(getId());
		// 4、设置 BeanFactory 的两个属性,是否允许 bean 覆盖、是否允许循环引用
		customizeBeanFactory(beanFactory);
		// 5、加载 Bean 定义----(详细见代码块三)
		loadBeanDefinitions(beanFactory);
		synchronized (this.beanFactoryMonitor) {
			this.beanFactory = beanFactory;
		}
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	}
}

createBeanFactory() 这个步骤返回的是一个 DefaultListableBeanFactory 类型,我们都知道 Spring 中有众多的 IOC 容器,为什么这里创建的是 DefaultListableBeanFactory 呢?我们可以通过下面这张继承关系图可以得出结论

Spring源码01---容器刷新前配置

通过上面这张图我们可以看出 ConfigurableListableBeanFactory 实现了第二层级的三个接口,并且它只有一个实现类 DefaultListableBeanFactory ,DefaultListableBeanFactory 这个实现类又通过继承 AbstractAutowireCapableBeanFactory 抽象类把整张图的功能都囊括了,这是其它的 BeanFactory 都做不到的,所以既然它的功能是最全的,这就是我们选择 DefaultListableBeanFactory 作为 BeanFactory 的实现类的原因.

代码块三、loadBeanDefinitions(beanFactory)

// XmlWebApplicationContext 中的方法
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	// 1、为指定的 beanFactory 创建一个 XmlBeanDefinitionReader
	XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

	// 2、设置环境信息
	beanDefinitionReader.setEnvironment(getEnvironment());
	// 3、将 XmlWebApplicationContext 赋值给 beanDefinitionReader 的 resourceLoader 属性
	// XmlWebApplicationContext 实现了 ResourceLoader 接口
	beanDefinitionReader.setResourceLoader(this);
	// 4、设置实体解析器
	beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

	// Allow a subclass to provide custom initialization of the reader,
	// then proceed with actually loading the bean definitions.
	// 5、空方法,留给子类去重写的方法
	initBeanDefinitionReader(beanDefinitionReader);
	// 6、加载 BeanDefinition(核心方法) ----(详情见代码块四)
	loadBeanDefinitions(beanDefinitionReader);
}

代码块四、loadBeanDefinitions(beanDefinitionReader)

// XmlWebApplicationContext 中的方法
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
	// 1、获取配置文件路径,如果 web.xml 中配置了 contextConfigLocation ,使用它的值作为 Spring 配置文件路径
	// 如果没有配置 contextConfigLocation ,那么就使用 spring 默认的配置文件 /WEB/INF/application.xml
	// (详情见代码块五)
	String[] configLocations = getConfigLocations();
	if (configLocations != null) {
		// 2、遍历配置文件路径,因为配置文件的路径可以指定多个
		for (String configLocation : configLocations) {
			// 3、根据其中的一个配置文件路径加载 BeanDefinitions ----(详情见代码块六)
			reader.loadBeanDefinitions(configLocation);
		}
	}
}

代码块五、getConfigLocations()

// AbstractRefreshableWebApplicationContext 类中的方法
public String[] getConfigLocations() {
	// 1、调用父类方法
	return super.getConfigLocations();
}

// AbstractRefreshableConfigApplicationContext 类中的方法
protected String[] getConfigLocations() {
	// 1、如果 web.xml 中配置了 contextConfigLocation ,使用该参数对应的值作为 spring 的配置文件
	// 2、如果没有配置,则使用 getDefaultConfigLocations()
	return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
}

// XmlWebApplicationContext 类中的方法
protected String[] getDefaultConfigLocations() {
	if (getNamespace() != null) {
		return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
	}
	else {
		// spring 默认的配置文件路径 /WEB/INF/application.xml
		return new String[] {DEFAULT_CONFIG_LOCATION};
	}
}

代码块六、loadBeanDefinitions(configLocation)

// AbstractBeanDefinitionReader 类中的方法
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
	// 1、调用本类中两个参数的构造方法,第二个参数值为 null ---- (详情见代码块七)
	return loadBeanDefinitions(location, null);
}

代码块七、loadBeanDefinitions(location, null)

// AbstractBeanDefinitionReader 类中的方法
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
	// 1、获取 ResourceLoader ,我们这里是 XmlWebApplicationContext
	ResourceLoader resourceLoader = getResourceLoader();
	// 2、如果类加载器为空,抛出异常
	if (resourceLoader == null) {
		throw new BeanDefinitionStoreException(
				"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
	}
	// 3、判断 resourceLoader 是否为 ResourcePatternResolver 的实例
	if (resourceLoader instanceof ResourcePatternResolver) {
		try {
			// 3.1、将 String 类型的配置文件名根据路径、前后缀等进行匹配获取到符合条件的配置文件,然后转成 Resource 类型的数组
			Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
      // 3.2、根据 resources 加载 BeanDefinitions ---- (详情见代码块八)
			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 + "]");
			}
			// 3.3、返回加载 BeanDefinition 的个数
			return loadCount;
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"Could not resolve bean definition resource pattern [" + location + "]", ex);
		}
	}
	else {
		// 4、通过绝对路径来加载资源,因为是绝对路径,只能加载一个配置文件
		Resource resource = resourceLoader.getResource(location);
		// 5、通过绝对路径来加载 BeanDefinitions
		int loadCount = loadBeanDefinitions(resource);
		if (actualResources != null) {
			actualResources.add(resource);
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
		}
		return loadCount;
	}
} 

代码块八、loadBeanDefinitions(resources)

// AbstractBeanDefinitionReader 类中的方法
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
	Assert.notNull(resources, "Resource array must not be null");
	int counter = 0;
	for (Resource resource : resources) {
		// 1、根据单个 Spring 配置文件加载 BeanDefinitions ---- (详情见代码块九)
		counter += loadBeanDefinitions(resource);
	}
	// 2、返回最后总共加载的 BeanDefinition
	return counter;
}

代码块九、loadBeanDefinitions(resource)

// XmlBeanDefinitionReader 类中的方法
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
	// 1、加载 BeanDefinition ---- (详情见代码块十)
	return loadBeanDefinitions(new EncodedResource(resource));
}

代码块十、loadBeanDefinitions(new EncodedResource(resource))

// XmlBeanDefinitionReader 类中的方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
	Assert.notNull(encodedResource, "EncodedResource must not be null");
	if (logger.isInfoEnabled()) {
		logger.info("Loading XML bean definitions from " + encodedResource.getResource());
	}

	Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
	if (currentResources == null) {
		currentResources = new HashSet<EncodedResource>(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());
			}
			// 1、核心方方法,执行加载 BeanDefiniton ----(详情见代码块十一)
			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();
		}
	}
}

代码块十一、doLoadBeanDefinitions(inputSource, encodedResource.getResource())

// XmlBeanDefinitionReader 类中的方法
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
	try {
		// 1、根据当前的一个 Spring 的配置文件创建 Document 对象
		Document doc = doLoadDocument(inputSource, resource);
		// 2、注册 BeanDefinition ---- (详情见代码块十二)
		return registerBeanDefinitions(doc, resource);
	}
	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);
	}
}

代码块十二、registerBeanDefinitions(doc, resource)

// XmlBeanDefinitionReader 类中的方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	// 1、创建 BeanDefinitionDocumentReader 对象
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	// 2、获取之前注册的 BeanDefinition 的个数
	int countBefore = getRegistry().getBeanDefinitionCount();
	// 3、注册 BeanDefinition ---- (详情见代码块十三)
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	// 4、返回本次注册的 BeanDefinition 总数目
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

代码块十三、registerBeanDefinitions(doc, createReaderContext(resource))

// DefaultBeanDefinitionDocumentReader 类中的方法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
	this.readerContext = readerContext;
	logger.debug("Loading bean definitions");
	// 1、获取 Document 元素的根节点, Spring 配置文件的根节点一般都是 beans
	Element root = doc.getDocumentElement();
	// 2、根据根节点注册 BeanDefinitions ---- (详情见代码块十四)
	doRegisterBeanDefinitions(root);
}

代码块十四、doRegisterBeanDefinitions(root)

protected void doRegisterBeanDefinitions(Element root) {
	// 1、
	BeanDefinitionParserDelegate parent = this.delegate;
	this.delegate = createDelegate(getReaderContext(), root, parent);
	// 1、校验 root 节点的命名空间是否为默认的命名空间(Spring 默认命名空间http://www.springframework.org/schema/beans)
	if (this.delegate.isDefaultNamespace(root)) {
		// 2、获取 Profile 属性
		String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
		if (StringUtils.hasText(profileSpec)) {
			String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
					profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			// 3、校验当前节点的 profile 是否符合当前环境定义的,,如果不是则直接跳过,不解析该节点下的内容
			if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
				if (logger.isInfoEnabled()) {
					logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
							"] not matching: " + getReaderContext().getResource());
				}
				return;
			}
		}
	}
	// 4、钩子方法,留给子类实现
	preProcessXml(root);
	// 5、解析 BeanDefinitions ---- (详情见代码块十五)
	parseBeanDefinitions(root, this.delegate);
	// 6、钩子方法,留给子类实现
	postProcessXml(root);
	this.delegate = parent;
}

profile 属性主要用于多环境开发,用于切换不同的环境,例如下图:

Spring源码01---容器刷新前配置

我们可以在配置文件中同时写上多套配置来适用于 dev 环境、sit 环境、uat 环境,这样可以方便的进行切换开发、部署环境,最常用的就是更换不同的数据库.具体使用哪个环境在 web.xml 中通过参数 spring.profiles.active 来配置,如下图

Spring源码01---容器刷新前配置

代码块十五、parseBeanDefinitions(root, this.delegate)

// DefaultBeanDefinitionDocumentReader 类中的方法
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
	if (delegate.isDefaultNamespace(root)) {
		NodeList nl = root.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (node instanceof Element) {
				Element ele = (Element) node;
				if (delegate.isDefaultNamespace(ele)) {
					// 1、解析默认名称空间下面的默认元素
					parseDefaultElement(ele, delegate);
				}
				else {
					// 2、解析默认名称空间下面的非默认元素
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		// 3、解析自定义命名空间下的元素
		delegate.parseCustomElement(root);
	}
}

先看一张图,这里面的不带前缀的就是默认的命名空间,对应的是: xmlns="http://www.springframework.org/schema/beans ,该空间中默认的元素有 import、alias、beans、description、import

Spring源码01---容器刷新前配置

默认空间下的非默认元素我们经常用到的主要有 <context:component-scan base-package="com.bocom"> 、<tx:annotation-driven/> 等,不过要使用这些标签需要引入对应的名称空间例如 context、tx

自定义的命名空间就是自己定义的名称空间

 

 

 

 

 

参考:https://joonwhee.blog.csdn.net/article/details/86563620

上一篇:xxe入门学习--------BUUCTF:[NCTF2019]Fake XML cookbook---------jarvisoj的API调用


下一篇:XML知识