Spring源码解析(二)——容器的基本实现

Spring源码解析(二)——容器的基本实现

Scroll Down

容器的基本用法

在通过Xml文件配置Bean的时候,我们可以直接使用XmlBeanFactory来获取Bean实例

public class BeanFactoryTest {
   @Test
   public void testSimpleLoad() {
       Resource resource = new ClassPathResource("beanFactoryTest.xml");
       BeanFactory beanFactory = new XmlBeanFactory(resource);
       MyTestBean myTestBean = (MyTestBean) beanFactory.getBean("myTestBean");
   }
}

这段代码完成的功能如下:

  • 读取配置文件beanFactoryTest.xml
  • 根据beanFactoryTest.xml中的配置找到对应的类的配置,并实例化
  • 调用实例化后的实例

Spring的结构组成

Spring中有两个核心的类,一个是DefaultListableBeanFactory,另一个是XmlBeanDefinitionReader

DefaultListableBeanFactory

XmlBeanFactory 继承自 DefaultListableBeanFactoryDefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册和加载bean的默认实现。

XmlBeanFactoryDefaultListableBeanFactory 区别在于前者使用了自定义的XML配置文件读取器 XmlBeanDefinitionReader

DefaultListableBeanFactory继承自 AbstractAutowireCapableBeanFactory 实现了 ConfigurableListableBeanFactoryBeanDefinitionRegistrySerializable 接口。

DefaultListableBeanFactory相关类图:
DefaultListableBeanFactory相关类图.png

容器加载相关类图:
容器加载相关类图

我们从上至下、由父到子进行简单了解每一个类或接口的功能:

  • AliasRegistry:定义对alias的简单增删查等操作
  • SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口 AliasRegistry 进行实现
  • SingletonBeanRegistry:定义接口对单例bean的注册及获取等
  • BeanFactory:定义接口用于获取bean及bean的各种属性等
  • DefaultSingletonBeanRegistry:对接口 SingletonBeanRegistry 的实现
  • HierarchicalBeanFactory:继承自 BeanFactory 接口,在其基础上增加对 parentFactory 支持
  • BeanDefinitionRegistry:定义对 BeanDefinition 的各种增删查操作
  • FactoryBeanRegistrySupport:在 DefaultSingletonBeanRegistry 的基础上增加对 FactoryBean 的特殊处理功能
  • ConfigurableBeanFactory:继承自 HierarchicalBeanFactory 和 SingletonBeanRegistry,提供配置 Factory 的各种方法
  • ListableBeanFactory:根据各种条件获取bean的配置清单
  • AbstractBeanFactory:抽象类,综合 FactoryBeanRegistrySupport 和 ConfigurableBeanFactory 的功能
  • AutowireCapableBeanFactory:继承 BeanFactory 接口,提供创建bean、自动注入、初始化以及应用bean的后处理器
  • AbstractAutowireCapableBeanFactory:综合 AbstractBeanFactory 并对接口 AutowireCapableBeanFactory 进行实现
  • ConfigurableListableBeanFactory:BeanFactory 配置清单,指定忽略类型及接口等
  • DefaultListableBeanFactory:综合上面所有功能,主要是对 Bean 注册后的处理
  • XmlBeanFactory:用于从XML文档中读取 BeanDefinition,主要添加利用 reader 属性读取注册资源文件

XmlBeanDefinitionReader

Spring通常使用注解和XML配置文件的方式来实现其功能,其大部分功能都是以配置作为切入点的,XmlBeanDefinitionReader 中包含了配置文件的读取、解析以及注册的大致流程。

XmlBeanDefinitionReader相关类图

  • ResourceLoader:定义资源加载器,主要用于根据给定的资源文件地址返回对应的Resource
  • BeanDefinitionReader:主要定义资源文件读取并转换为 BeanDefinition 的各个功能
  • EnvironmentCapable:定义获取Environment的方法
  • DocumentLoader:定义从资源文件加载到转换为Document的功能
  • AbstractBeanDefinitionReader:对 EnvironmentCapable 和 BeanDefinitionReader 定义的功能进行实现
  • BeanDefinitionDocumentReader:定义读取 Document 并注册 BeanDefinition 功能
  • BeanDefinitionParserDelegate:定义解析 Element 的各种方法

Spring读取XML配置文件的大致流程主要为:

  1. XmlBeanDefinitionReader 通过继承 AbstractBeanDefinitionReader 中的方法,使用 ResourceLoader 将资源路径转换为对应的 Resource 文件;
  2. 通过 DocumentLoaderResource 文件进行转换,将 Resource 文件转换为 Document 文件;
  3. 通过实现 BeanDefinitionDocumentReader 接口的 DefaultBeanDefinitionDocumentReader 类对 Document 进行解析,并使用 BeanDefinitionParserDelegateElement 进行解析。

容器的基础XmlBeanFactory

回头查看我们的测试代码

Resource resource = new ClassPathResource("beanFactoryTest.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);

观察 XmlBeanFactory 初始化的时序图来整体把握代码执行流程:

在测试的BeanFactoryTest中首先调用ClassPathResource 的构造函数来构造Resource资源文件的实例对象,这样后续的资源处理就可以用Resource 提供的各种服务来操作了,当我们有了Resource后就可以进行XmlBeanFactory的初始化了。

XmlBeanFactory初始化时序图

配置文件封装

Spring利用 ClassPathResource 读取配置文件,查看 ClassPathResource 的类继承结构,可以看到它实现了一个重要的接口 Resource。

/**
 * 封装任何能够返回InputStream的类,比如File、Classpath下的资源和Byte Array等
 */
public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
}
/**
 * 该接口抽象了所以Spring内部使用的底层资源:File、URL、Classpath...
 */
public interface Resource extends InputStreamSource {
    boolean exists();
    default boolean isReadable() {
        return exists();
    }
    default boolean isOpen() {
        return false;
    }
    default boolean isFile() {
        return false;
    }
    URL getURL() throws IOException;
    URI getURI() throws IOException;
    File getFile() throws IOException;
    default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(getInputStream());
    }
    long contentLength() throws IOException;
    long lastModified() throws IOException;
    Resource createRelative(String relativePath) throws IOException;
    @Nullable
    String getFilename();
    String getDescription();

}

对不同来源的资源文件都有相应的 Resource 实现:文件 (FileSystemResource)、Classpath资源 (ClassPathResource)、URL资源 (UrlResource)、InputStream资源 (InputStreamResource)、Byte数组 (ByteArrayResource) 等。

image.png

当通过Resource 相关类完成了对配置文件进行封装后配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理了。

回来继续探寻XmlBeanFactory 的初始化过程,XmlBeanFactory的初始化有若干办法,Spring中提供了很多的构造函数,在这里分析的是使用Resource实例作为构造函数参数的办法

	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}

	//构造函数内部再次调用内部构造函数:
	//parentBeanFactory 为父类BeanFactory用于Factory合并,可以为空
	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		//资源加载的真正实现
		this.reader.loadBeanDefinitions(resource);
	}

加载Bean

上一部分已经说到 XmlBeanDefinitionReader 中加载资源是依靠 loadBeanDefinitions(resource) 实现的。

首先先看看它的时序图:

image.png

观察时序图,可以得到它的大致流程为:

  1. 封装资源文件。当进入 XmlBeanDefinitionReader 后首先对参数 Resource 使用 EncodingResource 类进行封装;
  2. 获取输入流。从 Resource 中获取对应的 InputStream 并构造 InputSource
  3. 通过构造的 InputSource 实例和 Resource 实例继续调用函数 doLoadBeanDefinitions

我们来看一下loadBeanDefinitions函数具体的实现过程:

	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 {

			//encodedResource中获取已经封装的Resource对象,并再次从中获取InputStream
			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文件,包装 InputStream 成为 InputSource 。我们再去核心实现方法 doLoadBeanDefinitions看看

	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {


		//1.`getValidationModeForResource(resource)` 获取对XML 文件的验证模式。
		//2、`DocumentLoader.loadDocument()`加载XML 文件,并得到对应的Document
		//3.`registerBeanDefinitions(doc, resource)` 根据返回的Document对象注册Bean信息
		try {
			Document doc = doLoadDocument(inputSource, resource);
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
	}
	protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		// 委托DocumentLoader对象的loadDocument方法获取Document
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
	}

获取XML验证模式

上一部分已经说到Spring根据指定的XML文件加载bean,第一步需要判断XML文件的验证模式。XML常用的验证模式有两种:XSD和DTD。

DTD和XSD区别

使用DTD声明方式的配置文件:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
            "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    ...
</beans>

使用XSD声明方式的配置文件:

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans.xsd">
    ...
</beans>

在spring-beans模块的resource文件夹下包含了dtd和xsd的文件,Spring会对应去该目录下寻找相关文件对XML进行验证。

验证模式的读取

上面仅是对XML文件的两种验证模式做一个了解,具体Spring如何判断采用哪一种验证模式需要我们查看相应的 getValidationModeForResource(resource) 方法。

	//获取对XML配置文件的对应的验证模式(两种模式:xsd, dtd)
	protected int getValidationModeForResource(Resource resource) {
		int validationModeToUse = getValidationMode();
		// 如果手动指定了验证模式则使用指定的
		if (validationModeToUse != VALIDATION_AUTO) {
			return validationModeToUse;
		}
		// 没有手动指定则使用自动检测
		int detectedMode = detectValidationMode(resource);
		if (detectedMode != VALIDATION_AUTO) {
			return detectedMode;
		}
		return VALIDATION_XSD;
	}

自动检测验证模式的功能是在函数detectValidationMode方法中实现的,在
detectValidationMode函数中又将自动检测模式的的工作委托给了专门处理类XmlValidationModeDetector,调用了XmlValidationModeDetector的detectValidationMode方法

/**
  * 自动检测验证模式
  */
protected int detectValidationMode(Resource resource) {
    if (resource.isOpen()) {
        throw new BeanDefinitionStoreException(
            "Passed-in Resource [" + resource + "] contains an open stream: " +
            "cannot determine validation mode automatically. Either pass in a Resource " +
            "that is able to create fresh streams, or explicitly specify the validationMode " +  "on your XmlBeanDefinitionReader instance.");
    }

    InputStream inputStream;
    try {
        inputStream = resource.getInputStream();
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException(
            "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
            "Did you attempt to load directly from a SAX InputSource without specifying the " +
            "validationMode on your XmlBeanDefinitionReader instance?", ex);
    }

    try {
        // 委托XmlValidationModeDetector的detectValidationMode方法检测
        return this.validationModeDetector.detectValidationMode(inputStream);
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
                                               resource + "]: an error occurred whilst reading from the InputStream.", ex);
    }
}
/**
  * 真正实现检测XML资源文件的验证模式
  * 通过查看xml文件是否含有'DOCTYPE'判断是否是DTD模式
  */
public int detectValidationMode(InputStream inputStream) throws IOException {
    // Peek into the file to look for DOCTYPE.
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    try {
        boolean isDtdValidated = false;
        String content;
        while ((content = reader.readLine()) != null) {
            content = consumeCommentTokens(content);
            // 处理xml注释,如果是空行或注释则略过
            if (this.inComment || !StringUtils.hasText(content)) {
                continue;
            }
            if (hasDoctype(content)) {
                isDtdValidated = true;
                break;
            }
            // 检测一行是否拥有开始标签,例如<beans>, <bean>...DTD则是<?DOCTYPE>, 区别在于<后的是否是字母
            // 如果已经检测到是正式定义<beans>说明验证模式已经确定,无需继续读下去进行判断
            if (hasOpeningTag(content)) {
                // End of meaningful data...
                break;
            }
        }
        return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
    }
    catch (CharConversionException ex) {
        // Choked on some character encoding...
        // Leave the decision up to the caller.
        return VALIDATION_AUTO;
    }
    finally {
        reader.close();
    }
}

简而言之,就是检测XML开头部分是否含有 <?DOCTYPE 字符串,如果有表示是DTD验证模式,没有则是XSD

获取Document

第一步判断XML配置文件的验证模式完成后,紧接着利用DocumentLoader.loadDocument() 方法将XML资源文件转换为 Document 对象。同样,获取 Document 也不是 XmlValidationModeDetector 亲力亲为,而是交给了 DocumentLoader 去执行,DocumentLoader 是一个接口,真正调用的方法是在 DefaultDocumentLoader 实现类中实现的

/**
  * 根据包含Xml资源的InputSource获取Document
  */
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                             ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    if (logger.isDebugEnabled()) {
        logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
    }
    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource);
}

对XML文件的解析,主要是依靠JAXP (Java API for XMLProcessing) 实现的。同样来个链接:JAXP。有必要额外提及的是,这里利用 DocumentBuilderFactory 创建 DocumentBuilder 时,额外传入了一个 entityResolver 的对象。下面一部分具体谈谈 EntityResolver 是干嘛的。

protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
                                                @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
    throws ParserConfigurationException {

    DocumentBuilder docBuilder = factory.newDocumentBuilder();
    if (entityResolver != null) {
        docBuilder.setEntityResolver(entityResolver);
    }
    if (errorHandler != null) {
        docBuilder.setErrorHandler(errorHandler);
    }
    return docBuilder;
}

EntityResolver用法

EntityResolver entityResolver 参数是从 XmlBeanDefinitionReader 中传过来的,它是其中的一个属性,通过setter和getter方法可以设置与获取。和 InputSource 一样,EntityResolver 也是属于 org.xml.sax 包下的。SAX程序想要实现自定义处理外部实体必须实现 EntityResolver 接口并使用 setEntityResolver 方法向SAX驱动器注册一个实例。也就是说,对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明寻找相应的DTD定义,以便对文档进行验证。它默认寻找规则是通过声明的DTD的URI地址去网络下载DTD。这样可能引来一些问题,比如下载较慢,没有网络等。利用 EntityResolver 则可以通过读取本地DTD文件返回给SAX的方式来解决这一问题。

总结起来,EntityResolver 能够帮助我们通过程序来实现从本地自定义目录寻找dtd或者xsd验证文件而无需下载。

EntityResolver 本身是一个接口,内部定义了一个 resolveEntity 方法,接收两个参数 publicIdsystemId,返回 inputSource 对象。

public interface EntityResolver {
    public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;
}

对于不同的验证方式,其 publicId 和 systemId 是不同的。

如果解析验证模式为XSD的配置文件,那么参数为:

如果解析验证模式为DTD的配置文件,则参数为:

同样,Spring也将需要的验证文件保存在本地项目中,没错,就是之前在spring-beans模块的resource文件夹下的那些。

我们来深入看一看Spring如何找到文件的。前面已经分析 entityResolver 是通过getter方法获取的,本身 EntityResolver 也只是一个接口,需要一个实现类实现其定义的接口。我们具体从getter方法看起:

protected EntityResolver getEntityResolver() {
    if (this.entityResolver == null) {
        // Determine default EntityResolver to use.
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader != null) {
            this.entityResolver = new ResourceEntityResolver(resourceLoader);
        }
        else {
            this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
        }
    }
    return this.entityResolver;
}

可以看到,entityResolver 通过实例化 ResourceEntityResolver 或者 DelegatingEntityResolver 来获得,其中 ResourceEntityResolverDelegatingEntityResolver的子类,我们先了解 DelegatingEntityResolverresolveEntity() 方法的实现。

@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
    if (systemId != null) {
        if (systemId.endsWith(DTD_SUFFIX)) {
            // 截取systemId最后的xxx.dtd然后去当前路径下搜索
            return this.dtdResolver.resolveEntity(publicId, systemId);
        }
        else if (systemId.endsWith(XSD_SUFFIX)) {
            // 如果是xsd,调用META-INF/Spring.schemas解析
            return this.schemaResolver.resolveEntity(publicId, systemId);
        }
    }
    return null;
}

解析注册BeanDefinitions

Spring根据指定的XML文件加载bean的三步我们已经走完了两步,最后一步是根据返回的Document注册Bean信息,这一部分也是真正的重头戏。进入 registerBeanDefinitions(doc, resource) 方法:

/**
  * 通过Document对象提取注册所有的bean
  */
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 利用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 记录统计前BeanDefinition的个数
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 加载注册bean
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 记录本次加载的BeanDefinition个数
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

可以看到注册bean的工作最终交给了 BeanDefinitionDocumentReader 类来专门处理,典型的单一职责原则。该对象的实例化则是通过 createBeanDefinitionDocumentReader() 完成,在该方法中实例化的对象其实是 DefaultBeanDefinitionDocumentReader 类型,进入该类查看其 registerBeanDefinitions 方法:

/**
  * 注册加载bean
  */
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    // 提取Document的root,也就是<beans>...</beans>
    Element root = doc.getDocumentElement();
    // 将root内容进行解析 所以这才是真正开始注册加载bean的实现了:)
    doRegisterBeanDefinitions(root);
}

可以看到之前一系列的准备工作,读取XML,获得Document,再提取rootElement,最终对root内容进行解析,调用 doRegisterBeanDefinitions(root) 方法注册加载bean

/**
  * 根据<bean>...</bean>注册所有的bean,核心之处  
  */
@SuppressWarnings("deprecation")  // for Environment.acceptsProfiles(String...)
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 parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    if (this.delegate.isDefaultNamespace(root)) {
        // 处理profile属性,profile属性是配置部署环境的(生产、开发),方便切换环境
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            // profile可以同时指定多个,需要进行拆分,并解析每个profile都是符合环境变量定义的,没定义则不解析
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            // We cannot use Profiles.of(...) since profile expressions are not supported
            // in XML config. See SPR-12458 for details.
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                 "] not matching: " + getReaderContext().getResource());
                }
                return;
            }
        }
    }

    // 解析前处理,留给子类实现,在解析Bean的前后进行处理
    preProcessXml(root);
    parseBeanDefinitions(root, this.delegate);
    // 解析后处理,留给子类实现
    postProcessXml(root);

    this.delegate = parent;
}

上面代码的逻辑,首先是对 profile 的处理,在正式进行解析xml注册bean的前后,各有一个方法用来做一些准备工作和收尾工作,分别是 preProcessXml(root)postProcessXml(root) 方法。这两个方法采用了模板方法设计模式

Profile属性使用

关于XML中如何配置使用 Profile 属性不再赘述,因为不同环境Spring需要合理的进行解XML配置bean,所以在之前先对XML中 Profile 属性先进行相关处理。首先会通过 root.getAttribute(PROFILE_ATTRIBUTE) 判断 标签是否含有 profile 属性,如果有则会需要到环境变量中寻找,getReaderContext().getEnvironment() 获得环境变量。profile是可以同时指定多个,需要进行拆分,并解析每个profile都是符合环境变量定义的,没定义则不解析。

解析注册BeanDefinition

接下来进入parseBeanDefinitions(root, this.delegate) 方法:

/**
  * 解析注册每一个<bean>...</bean>,默认的或者自定义的如:<tx: .../>
  * 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) {
    // 对beans的处理
    // 判断是否是自定义bean声明
    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;
                // 判断是否是自定义bean声明
                if (delegate.isDefaultNamespace(ele)) {
                    // 对bean的处理
                    parseDefaultElement(ele, delegate);
                } else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        delegate.parseCustomElement(root);
    }
}

因为Spring的XML配置文件中允许我们使用自定义的标签,所以Spring对 Element 处理时需要区分情况。默认的Bean声明比如:

<bean id="myTestBean" class="guo.ping.ioc.bean.MyTestBean" />

典型的自定义比如配置事务的标签 tx:

<tx:annotation-driven/>

如果根节点或者子节点采用自带的标签,Spring完全知道如何进行解析,使用 parseDefaultElement 方法即可,否则需要使用 delegate.parseCustomElement(root) 来进行解析。至于Spring如何判断是否是自定义标签,采用 delegate.isDefaultNamespace(ele) 来判断,进入源码最终发现如下代码:

public boolean isDefaultNamespace(Node node) {
    return isDefaultNamespace(getNamespaceURI(node));
}

public boolean isDefaultNamespace(@Nullable String namespaceUri) {
    return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}

命名空间 BEANS_NAMESPACE_URIhttp://www.springframework.org/schema/beans,如果获取节点的命名空间也是这个则认为是默认标签,否则就是自定义标签。