本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Spring IOC容器初始化过程(二)配置信息加载与注册
上文中 Spring IOC容器初始化过程(一)资源定位过程 我们一起分析了资源定位的过程,Spring IOC 容器在初始化的过程中并不是直接读取配置文件然后进行加载,而是在开始做了许多准备工作,先初始化资源读取器,设置其读取策略,校验策略,再将数据加载至内存中,最后封装为BeanDefinition对象进行注册。
整体加载的时序图
工程目录
POM文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springTest</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
</project>
测试代码
public class ServiceB {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Object serviceA = context.getBean("serviceA");
System.out.println(serviceA);
}
}
xml配置
<?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"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:p="http://www.springframework.org/schema/p"
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.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<context:component-scan base-package="com.donkeys.spring"/>
<bean id="serviceA" class="com.donkeys.spring.service.ServiceA"></bean>
</beans>
加载过程
接上文,上文中我们已经获取到了ClassPathXmlApplicationContext的实例化对象返回的配置文件路径信息,同时调用了资源读取器的loadBeanDefinitions方法,下面从这个方法开始,正式进入到资源加载过程
XmlBeanDefinitionReader 的loadBeanDefinitions方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException;
/**
* 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 {
//判断传入的xml文件资源是否为空
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
//resourcesCurrentlyBeingLoaded 是一个ThreadLocal 变量,从当前线程变量获取当前的资源描述文件
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());
}
//开始真正执行读取Bean定义的代码
//从输入流中读取Spring Bean的定义
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();
}
}
}
这个方法中做了2件事
- 判断当前资源描述文件是否重复加载
- 如果没有重复加载,则将当前资源描述文件,转换为一个输入流,让reader进行读取
真正读取的方法是在 doLoadBeanDefinitions 中执行的
XmlBeanDefinitionReader 的doLoadBeanDefinitions 方法
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException ;
/**
* Actually load bean definitions from the specified XML file.
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
* @see #doLoadDocument
* @see #registerBeanDefinitions
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//doLoadDocument方法,将输入流转换为一个文档流。同时这个方法内部,还对资源进行了校验
Document doc = doLoadDocument(inputSource, resource);
//开始进行注册
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);
}
}
该方法中,将输入流转换为了一个文档流,最后调用了注册方法。我们可以先看一下doLoadDocument 方法,
/**
* Actually load the specified document using the configured DocumentLoader.
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
* @return the DOM Document
* @throws Exception when thrown from the DocumentLoader
* @see #setDocumentLoader
* @see DocumentLoader#loadDocument
*/
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
可以看到,这个方法的返回值直接调用了loadDocument方法,同时,还做了2件事,
- 先获取了实体解析器
- 然后获取了资源文件的校验方式,真正的校验工作是在后面进行的。我们继续往下看
XmlBeanDefinitionReader 的成员变量 documentLoader 的类为 DefaultDocumentLoader 。所以我们直接看 DefaultDocumentLoader 的 loadDocument 方法
/**
* Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
* XML parser.
*/
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
//静态工厂创建一个DocumentBuilderFactory对象,同时这个方法还会根据validationMode生成xml文件校验规则
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
//生成DocumentBuilder对象,用于解析传入的文档流
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
//解析文档流,返回文档对象,解析过程中进行校验
return builder.parse(inputSource);
}
到这里,文档被解析为了文档对象,配置文件已经完全加载到了内存中,剩下的就是解析,注册就完成了容器的初始化工作
下面回到XmlBeanDefinitionReader 的doLoadBeanDefinitions 方法,进入到注册过程
注册过程
准备进入registerBeanDefinitions 方法中
XmlBeanDefinitionReader 的registerBeanDefinitions方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException;
/**
* Register the bean definitions contained in the given DOM document.
* Called by {@code loadBeanDefinitions}.
* <p>Creates a new instance of the parser class and invokes
* {@code registerBeanDefinitions} on it.
* @param doc the DOM document
* @param resource the resource descriptor (for context information)
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of parsing errors
* @see #loadBeanDefinitions
* @see #setDocumentReaderClass
* @see BeanDefinitionDocumentReader#registerBeanDefinitions
*/
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//创建bean定义文档读取器用于读取给定DOM文档中包含的bean定义。
//可以看到Spring 中分工是很明确的,刚刚读取文档的类叫做 XmlBeanDefinitionReader,他把xml文件转换为了文档对象
//这里读取文档中bean定义的就是BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//注册前beanDefinition 的数量
int countBefore = getRegistry().getBeanDefinitionCount();
//解析并注册
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//注册后beanDefinition 的数量差
return getRegistry().getBeanDefinitionCount() - countBefore;
}
DefaultBeanDefinitionDocumentReader类
/**
* This implementation parses bean definitions according to the "spring-beans" XSD
* (or DTD, historically).
* <p>Opens a DOM Document; then initializes the default settings
* specified at the {@code <beans/>} level; then parses the contained bean definitions.
*/
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//获取文档根对象
Element root = doc.getDocumentElement();
//开始执行注册方法
doRegisterBeanDefinitions(root);
}
/**
* Register each bean definition within the given root {@code <beans/>} element.
*/
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
/**
* 生成一个BeanDefinitionParserDelegate 对象
* 该类中定义了关于 spring 的xml配置文件的关键字常量配置
* 这里又看到了分工明确的类,生成一个BeanDefinitionParserDelegate对象就是用于解析节点中Bean的定义信息的
*/
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
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;
}
}
}
//在解析Bean 定义之前,进行自定义的解析,增强解析过程的可扩展性
//留给子类实现
/**
* 模板设计模式,
* 如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些处理的话,
* 那么只需要重写这两个方法就可以了
*/
preProcessXml(root);
//从Document的根元素进行Bean定义的Document对象
//从根元素开始对文档进行解析,解析过程是由delegate对象完成,生成一个BeanDefinitionHolder对象,包含配置文件中所有的配置信息
//Spring 的配置文件中,不止有bean 关键字,还有Import,Alias等关键,这里我们只讨论bean的解析
parseBeanDefinitions(root, this.delegate);
//解析后处理,由子类实现
postProcessXml(root);
this.delegate = parent;
}
可以看到DefaultBeanDefinitionDocumentReader的 doRegisterBeanDefinitions 方法中做了3件事
- 生成了一个用于解析文档配置的代表BeanDefinitionParserDelegate对象
- 配置了2个模板方法分别提供给用户用于在加载beanDefinition 之前和之后做操作
- 真正的解析方法在parseBeanDefinitions中执行
下面进入parseBeanDefinitions方法中
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root the DOM root element of the document
*/
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)) {
//解析默认的元素
parseDefaultElement(ele, delegate);
}
else {
//解析自定义的元素
delegate.parseCustomElement(ele);
}
}
}
}
else {
//解析自定义的元素
delegate.parseCustomElement(root);
}
}
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//如果是import 节点,则进行导入解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//如果元素节点是Alias 别名元素,则进行别名解析
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//元素节点既不是导入元素,也不是别名元素,那就是普通的bean元素
//按照Spring 的Bean 规则解析元素
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
//对beans 标签进行处理
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
/**
* Process the given bean element, parsing the bean definition
* and registering it with the registry.
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//通过委托对象,解析当前元素,返回一个BeanDefinitionHolder对象,该对象中会包含我们在配置文件中给于的属性
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//这里会判断是否该元素下有自定义的元素,如果有则将其加载进来
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
//对bdHolder进行注册,
//委托设计模式
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
//在容器上下文中,发出现响应事件,通知相关的监听器,这个bean的相关配置已经加载完了
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
这里列出了三段代码,
第一段代码:
从文档根节点开始向下遍历,依次判断该文档节点元素是默认元素还是用户自定义元素,如果是自定义元素,则进行自定义元素解析,否则按照默认元素进行解析
第二段代码:
这段代码是默认元素的执行的逻辑,这里对默认元素再次进行了分类,分别分为import 标签,alias标签,bean 标签,beans标签
我们主要讨论bean标签,这几个方法都比较类似
第三段代码:
这段代码主要描述了bean标签的解析过程,首先delegate根据传入的节点元素生成了BeanDefinitionHolder对象,这个对象包含了这个bean的所有配置信息。然后开始加载自定义元素,自定义元素也需要用户提供方法去解析。
最后调用了BeanDefinitionReaderUtils工具类的静态方法registerBeanDefinition,在容器中注册了这个BeanDefinitionHolder对象
下面我们继续看BeanDefinitionReaderUtils的registerBeanDefinition方法
BeanDefinitionReaderUtils的静态方法registerBeanDefinition
/**
* Register the given bean definition with the given bean factory.
* @param definitionHolder the bean definition including name and aliases
* @param registry the bean factory to register with
* @throws BeanDefinitionStoreException if registration failed
*/
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
//获取当前definitionHolder的名称,这个名称可以用户定义,如果用户没有定义,则spring会自动为其生成一个name,
//name 的生成规则是 首先获取这个类的类名
//然后判断是否是内部bean,如果是,则为 类名#hash值
//不是内部bean ,则判断当前容器是否有同名的,没有,则为: 类名
//有同名,设置了一个计数器,如果当前有2个同名,那么该计数器最后值为3 则最终名称为 类名#3
//这个取名过程是在生成这个BeanDefinitionHolder对象时做的
String beanName = definitionHolder.getBeanName();
//开始进行注册
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
BeanDefinitionRegistry接口的registerBeanDefinition方法
BeanDefinitionRegistry是一个接口,这里的主要实现类是DefaultListableBeanFactory类
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
//判断beanName名是否为空
Assert.hasText(beanName, "Bean name must not be empty");
//判断beanDefinition对象是否为空
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
//注册前的最后一次校验工作,这里的校验不是校验XML文件
//这里主要对AbstractBeanDefinition 属性的methodOverrides校验
//校验methodOverrides是否与工厂方法并存
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition oldBeanDefinition;
//处理已经注册的beanName 的情况,
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
//如果该beanName已经注册,且在配置中配置了bean不允许被覆盖,则抛出异常
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(oldBeanDefinition)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
//所有处理完成,将该beanDefinition加入Map缓存
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
在进行为空校验之后,分别处理了2种情况
- 容器中已经有这个beanName对象存在
- 这里会获取配置,是否允许覆盖,如果不允许覆盖,则会抛出异常
- 容器中没有这个beanName对象存在
这两个方法的正常流程走完都是向beanDefinitionMap中加入该bean的定义。 至此,Spring IOC容器的加载过程全部结束。配置文件中的所有bean 定义都被加入了beanDefinitionMap中