【Spring实战】----Spring配置文件的解析

一、背景知识

Spring的核心的核心就是bean的配置及管理,至Spring最新发布的版本4.3.2已经有三种方式可以配置bean:

1)在XML中进行显示配置

2)在Java中进行显示配置

3)隐式的bean发现机制和自动装配

上述三种配置不展开说明,而且目前用的较多的是第3种(当然XML配置文件的使用仍然占据了不可替代的位置),可参考《Spring in Aciton 第四版》。但Spring最初的发展都是基于XML的显示配置的。本篇文章也是分析Spring配置文件的解析的。


二、本篇要解决的问题

1)Spring解析配置文件时如何根据标签元素查询到命名空间的?

2)如何根据xsd文件对配置文件中的元素进行校验的?

3)默认标签和自定义标签的解析过程

其实上述两个问题和Spring也没有太大的关系,上述操作都是在XML解析器中做的,解析器是这样一个程序:它读入一个文件,确认这个文件具有正确的格式,然后将其分解成各个元素,使的程序员能够访问这些元素。Java库提供了两种XML解析器:DOM(Document Object Model,树型解析器)和SAX(Simple API for XML,流机制解析器)。Spring对XML的操作也是建立在此基础上的。


具体Spring配置bean的过程可参考《Spring源码深度解析》


三、分析

以Spring配置数据源的配置文件为例:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:tx="http://www.springframework.org/schema/tx"
	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.2.xsd
		http://www.springframework.org/schema/mvc 
		http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-4.2.xsd 
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx-4.2.xsd ">

	<context:property-placeholder location="classpath:jdbc.properties"/>
	
	<!-- 配置数据源  -->	
	<!-- 配置C3P0连接池: -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driver}"/>
		<property name="jdbcUrl" value="${jdbc.url}"/>
		<property name="user" value="${jdbc.user}"/>
		<property name="password" value="${jdbc.password}"/>
		<!--每5小时检查所有连接池中的空闲连接。防止mysql wait_timeout(默认的为8小时) -->
		<property name="idleConnectionTestPeriod" value="18000"/>	
	</bean>
	
	<!-- sqlSessionFactory -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 数据库连接池 -->
		<property name="dataSource" ref="dataSource" />
		<!-- 加载mybatis的全局配置文件 -->
		<property name="configLocation" value="classpath:sqlMapConfig.xml" />
	</bean>
	
	<!-- mapper扫描器 -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<!-- 扫描包路径,如果需要扫描多个包,中间使用半角逗号隔开 -->
		<property name="basePackage" value="com.mango.mapper"></property>
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
	</bean>

</beans>


在Spring配置文件中经常能看到像上述配置文件中开头一样一坨的url,当然上述其中有好多在配置文件中没有使用到。先科普下:直接摘抄了

实际上xsd是dtd的替代品,xsd比dtd更强大。看了xsd的介绍,就可以知道上述一坨是干什么用的了,像eclipse中就有基于xsd对xml元素的校验功能,如我们把context命名命名空间删除,在标签

<context:property-placeholder location="classpath:jdbc.properties"/>

上就会报如下错误:

The prefix "context" for element "context:property-placeholder" is not bound.


这个报错并不影响程序的执行,在程序中xml解析器也会对其校验,这个时候就会出异常,下面我们就逐一分析(二)中提到的两个问题:

1)Spring解析配置文件时如何根据标签元素查询到命名空间的?

解析配置文件时,命名空间相关信息都放置在fNamespace中,NamespaceSupport.java

 /**
     * Namespace binding information. This array is composed of a
     * series of tuples containing the namespace binding information:
     * <prefix, uri>. The default size can be set to anything
     * as long as it is a power of 2 greater than 1.
     *
     * @see #fNamespaceSize
     * @see #fContext
     */
    protected String[] fNamespace = new String[16 * 2];


其中包含前缀和命名空间:

[xml, http://www.w3.org/XML/1998/namespace, xmlns, http://www.w3.org/2000/xmlns/, , http://www.springframework.org/schema/beans, aop, http://www.springframework.org/schema/aop, xsi, http://www.w3.org/2001/XMLSchema-instance, context, http://www.springframework.org/schema/context, mvc, http://www.springframework.org/schema/mvc, tx, http://www.springframework.org/schema/tx, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]


那么根据元素获取命名空间就可以理解了:

/**
     * Look up a prefix and get the currently-mapped Namespace URI.
     * <p>
     * This method looks up the prefix in the current context. If no mapping
     * is found, this methods will continue lookup in the parent context(s).
     * Use the empty string ("") for the default Namespace.
     *
     * @param prefix The prefix to look up.
     *
     * @return The associated Namespace URI, or null if the prefix
     *         is undeclared in this context.
     */

public String getURI(String prefix) {

        // find prefix in current context
        for (int i = fNamespaceSize; i > 0; i -= 2) {
            if (fNamespace[i - 2] == prefix) {
                return fNamespace[i - 1];
            }
        }

        // prefix not found
        return null;

    }


比如:

<context:property-placeholder location="classpath:jdbc.properties"/>

会先根据context前缀找到http://www.springframework.org/schema/context命名空间。如果不配置命名空间的异常:

2016-11-21 16:51:00.548 [localhost-startStop-1] ERROR org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:351) Context initialization failed
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 19 in XML document from file [D:\software\Server\Tomcat\apache-tomcat-7.0.30\webapps\ETeam\WEB-INF\classes\applicationContext-dao.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 19; columnNumber: 70; 元素 "context:property-placeholder" 的前缀 "context" 未绑定。
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:399) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:181) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:217) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:125) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:94) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:129) ~[spring-context-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:612) ~[spring-context-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:513) ~[spring-context-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:444) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:326) [spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:107) [spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4791) [catalina.jar:7.0.30]
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5285) [catalina.jar:7.0.30]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:7.0.30]
	at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901) [catalina.jar:7.0.30]
	at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877) [catalina.jar:7.0.30]
	at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:618) [catalina.jar:7.0.30]
	at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1100) [catalina.jar:7.0.30]
	at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1618) [catalina.jar:7.0.30]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [?:1.7.0_79]
	at java.util.concurrent.FutureTask.run(FutureTask.java:262) [?:1.7.0_79]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [?:1.7.0_79]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [?:1.7.0_79]
	at java.lang.Thread.run(Thread.java:745) [?:1.7.0_79]
Caused by: org.xml.sax.SAXParseException: 元素 "context:property-placeholder" 的前缀 "context" 未绑定。
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:198) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.fatalError(ErrorHandlerWrapper.java:177) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:441) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:368) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:325) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:289) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2786) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:606) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:117) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:848) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:243) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:347) ~[?:1.7.0_79]
	at org.springframework.beans.factory.xml.DefaultDocumentLoader.loadDocument(DefaultDocumentLoader.java:76) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadDocument(XmlBeanDefinitionReader.java:429) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:391) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	... 26 more


就因找不到命名空间而报错:

if (fElementQName.prefix != null && fElementQName.uri == null) {
                fErrorReporter.reportError(XMLMessageFormatter.XMLNS_DOMAIN,
                        "ElementPrefixUnbound",
                        new Object[]{fElementQName.prefix, fElementQName.rawname},
                        XMLErrorReporter.SEVERITY_FATAL_ERROR);
            }

总结:sax或者dom解析器首先将xml文件转换成内存中的文档document,这个时候会吧标签对应的命名空间解析出来并保存到Node中,后面根据node.getNamespaceURI()就可以获取标签对应的命名空间。

2)如何根据xsd文件对配置文件中的元素进行校验的?

首先会找到xsd文件:科普


DelegatingEntityResolver.java
@Override
	public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
		if (systemId != null) {
			if (systemId.endsWith(DTD_SUFFIX)) {
				return this.dtdResolver.resolveEntity(publicId, systemId);
			}
			else if (systemId.endsWith(XSD_SUFFIX)) {
				return this.schemaResolver.resolveEntity(publicId, systemId);
			}
		}
		return null;
	}

根据不同类型加载xsd文件,PluggableSchemaResolver.java

@Override
	public InputSource resolveEntity(String publicId, String systemId) throws IOException {
		if (logger.isTraceEnabled()) {
			logger.trace("Trying to resolve XML entity with public id [" + publicId +
					"] and system id [" + systemId + "]");
		}

		if (systemId != null) {
			String resourceLocation = getSchemaMappings().get(systemId);
			if (resourceLocation != null) {
				Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
				try {
					InputSource source = new InputSource(resource.getInputStream());
					source.setPublicId(publicId);
					source.setSystemId(systemId);
					if (logger.isDebugEnabled()) {
						logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
					}
					return source;
				}
				catch (FileNotFoundException ex) {
					if (logger.isDebugEnabled()) {
						logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
					}
				}
			}
		}
		return null;
	}


从META-INF/spring.schemas中加载

/**
	 * The location of the file that defines schema mappings.
	 * Can be present in multiple JAR files.
	 */
	public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
/**
	 * Load the specified schema mappings lazily.
	 */
	private Map<String, String> getSchemaMappings() {
		if (this.schemaMappings == null) {
			synchronized (this) {
				if (this.schemaMappings == null) {
					if (logger.isDebugEnabled()) {
						logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
					}
					try {
						Properties mappings =
								PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
						if (logger.isDebugEnabled()) {
							logger.debug("Loaded schema mappings: " + mappings);
						}
						Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(mappings.size());
						CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
						this.schemaMappings = schemaMappings;
					}
					catch (IOException ex) {
						throw new IllegalStateException(
								"Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
					}
				}
			}
		}
		return this.schemaMappings;
	}


从classpath下加载所有的

/**
	 * Load all properties from the specified class path resource
	 * (in ISO-8859-1 encoding), using the given class loader.
	 * <p>Merges properties if more than one resource of the same name
	 * found in the class path.
	 * @param resourceName the name of the class path resource
	 * @param classLoader the ClassLoader to use for loading
	 * (or {@code null} to use the default class loader)
	 * @return the populated Properties instance
	 * @throws IOException if loading failed
	 */
	public static Properties loadAllProperties(String resourceName, ClassLoader classLoader) throws IOException {
		Assert.notNull(resourceName, "Resource name must not be null");
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = ClassUtils.getDefaultClassLoader();
		}
		Enumeration<URL> urls = (classLoaderToUse != null ? classLoaderToUse.getResources(resourceName) :
				ClassLoader.getSystemResources(resourceName));
		Properties props = new Properties();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			URLConnection con = url.openConnection();
			ResourceUtils.useCachesIfNecessary(con);
			InputStream is = con.getInputStream();
			try {
				if (resourceName.endsWith(XML_FILE_EXTENSION)) {
					props.loadFromXML(is);
				}
				else {
					props.load(is);
				}
			}
			finally {
				is.close();
			}
		}
		return props;
	}

spring-bean中的spring.schemas文件

http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans-4.0.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans-4.1.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.2.xsd=org/springframework/beans/factory/xml/spring-beans-4.2.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.3.xsd=org/springframework/beans/factory/xml/spring-beans-4.3.xsd
http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-4.3.xsd
http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
http\://www.springframework.org/schema/tool/spring-tool-4.0.xsd=org/springframework/beans/factory/xml/spring-tool-4.0.xsd
http\://www.springframework.org/schema/tool/spring-tool-4.1.xsd=org/springframework/beans/factory/xml/spring-tool-4.1.xsd
http\://www.springframework.org/schema/tool/spring-tool-4.2.xsd=org/springframework/beans/factory/xml/spring-tool-4.2.xsd
http\://www.springframework.org/schema/tool/spring-tool-4.3.xsd=org/springframework/beans/factory/xml/spring-tool-4.3.xsd
http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-4.3.xsd
http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
http\://www.springframework.org/schema/util/spring-util-4.0.xsd=org/springframework/beans/factory/xml/spring-util-4.0.xsd
http\://www.springframework.org/schema/util/spring-util-4.1.xsd=org/springframework/beans/factory/xml/spring-util-4.1.xsd
http\://www.springframework.org/schema/util/spring-util-4.2.xsd=org/springframework/beans/factory/xml/spring-util-4.2.xsd
http\://www.springframework.org/schema/util/spring-util-4.3.xsd=org/springframework/beans/factory/xml/spring-util-4.3.xsd
http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-4.3.xsd


找到xsd文件后就根据其中的内容判断xml中元素是否应用正确:例如将property改为ref

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<ref name="driverClass" value="${jdbc.driver}"/>            //bean元素下面并没有ref元素,所以会保存
		<property name="jdbcUrl" value="${jdbc.url}"/>
		<property name="user" value="${jdbc.user}"/>
		<property name="password" value="${jdbc.password}"/>
		<!--每5小时检查所有连接池中的空闲连接。防止mysql wait_timeout(默认的为8小时) -->
		<property name="idleConnectionTestPeriod" value="18000"/>	
	</bean>


报错如下:

Multiple annotations found at this line:
	- cvc-complex-type.3.2.2: Attribute 'value' is not allowed to appear in element 'ref'.
	- cvc-complex-type.3.2.2: Attribute 'name' is not allowed to appear in element 'ref'.
	- cvc-complex-type.2.4.a: Invalid content was found starting with element 'ref'. One of '{"http://
	 www.springframework.org/schema/beans":description, "http://www.springframework.org/schema/
	 beans":meta, "http://www.springframework.org/schema/beans":constructor-arg, "http://
	 www.springframework.org/schema/beans":property, "http://www.springframework.org/schema/
	 beans":qualifier, "http://www.springframework.org/schema/beans":lookup-method, "http://
	 www.springframework.org/schema/beans":replaced-method, WC[##other:"http://
	 www.springframework.org/schema/beans"]}' is expected.


执行代码: XMLSchemaValidator.java

// if we are not skipping this element, and there is a content model,
        // we try to find the corresponding decl object for this element.
        // the reason we move this part of code here is to make sure the
        // error reported here (if any) is stored within the parent element's
        // context, instead of that of the current element.
        Object decl = null;
        if (fCurrentCM != null) {
            decl = fCurrentCM.oneTransition(element, fCurrCMState, fSubGroupHandler);
            // it could be an element decl or a wildcard decl
            if (fCurrCMState[0] == XSCMValidator.FIRST_ERROR) {
                XSComplexTypeDecl ctype = (XSComplexTypeDecl) fCurrentType;
                //REVISIT: is it the only case we will have particle = null?
                Vector next;
                if (ctype.fParticle != null
                    && (next = fCurrentCM.whatCanGoHere(fCurrCMState)).size() > 0) {
                    String expected = expectedStr(next);
                    reportSchemaError(
                        "cvc-complex-type.2.4.a",
                        new Object[] { element.rawname, expected });
                } else {
                    reportSchemaError("cvc-complex-type.2.4.d", new Object[] { element.rawname });
                }
            }
        }

XSDFACM.java

/**
     * one transition only
     *
     * @param curElem The current element's QName
     * @param state stack to store the previous state
     * @param subGroupHandler the substitution group handler
     *
     * @return  null if transition is invalid; otherwise the Object corresponding to the
     *      XSElementDecl or XSWildcardDecl identified.  Also, the
     *      state array will be modified to include the new state; this so that the validator can
     *      store it away.
     *
     * @exception RuntimeException thrown on error
     */
    public Object oneTransition(QName curElem, int[] state, SubstitutionGroupHandler subGroupHandler) {
        int curState = state[0];

        if(curState == XSCMValidator.FIRST_ERROR || curState == XSCMValidator.SUBSEQUENT_ERROR) {
            // there was an error last time; so just go find correct Object in fElemmMap.
            // ... after resetting state[0].
            if(curState == XSCMValidator.FIRST_ERROR)
                state[0] = XSCMValidator.SUBSEQUENT_ERROR;

            return findMatchingDecl(curElem, subGroupHandler);
        }

        int nextState = 0;
        int elemIndex = 0;
        Object matchingDecl = null;

        for (; elemIndex < fElemMapSize; elemIndex++) {
            nextState = fTransTable[curState][elemIndex];
            if (nextState == -1)
                continue;
            int type = fElemMapType[elemIndex] ;
            if (type == XSParticleDecl.PARTICLE_ELEMENT) {
                matchingDecl = subGroupHandler.getMatchingElemDecl(curElem, (XSElementDecl)fElemMap[elemIndex]);
                if (matchingDecl != null) {
                    // Increment counter if constant space algorithm applies
                    if (fElemMapCounter[elemIndex] >= 0) {
                        fElemMapCounter[elemIndex]++;
                    }
                    break;
                }
            }
            else if (type == XSParticleDecl.PARTICLE_WILDCARD) {
                if (((XSWildcardDecl)fElemMap[elemIndex]).allowNamespace(curElem.uri)) {
                    matchingDecl = fElemMap[elemIndex];
                    // Increment counter if constant space algorithm applies
                    if (fElemMapCounter[elemIndex] >= 0) {
                        fElemMapCounter[elemIndex]++;
                    }
                    break;
                }
            }
        }

        // if we still can't find a match, set the state to first_error
        // and return null
        if (elemIndex == fElemMapSize) {
            state[1] = state[0];
            state[0] = XSCMValidator.FIRST_ERROR;
            return findMatchingDecl(curElem, subGroupHandler);
        }

        if (fCountingStates != null) {
            Occurence o = fCountingStates[curState];
            if (o != null) {
                if (curState == nextState) {
                    if (++state[2] > o.maxOccurs &&
                        o.maxOccurs != SchemaSymbols.OCCURRENCE_UNBOUNDED) {
                        // It's likely that we looped too many times on the current state
                        // however it's possible that we actually matched another particle
                        // which allows the same name.
                        //
                        // Consider:
                        //
                        // <xs:sequence>
                        //  <xs:element name="foo" type="xs:string" minOccurs="3" maxOccurs="3"/>
                        //  <xs:element name="foo" type="xs:string" fixed="bar"/>
                        // </xs:sequence>
                        //
                        // and
                        //
                        // <xs:sequence>
                        //  <xs:element name="foo" type="xs:string" minOccurs="3" maxOccurs="3"/>
                        //  <xs:any namespace="##any" processContents="skip"/>
                        // </xs:sequence>
                        //
                        // In the DFA there will be two transitions from the current state which
                        // allow "foo". Note that this is not a UPA violation. The ambiguity of which
                        // transition to take is resolved by the current value of the counter. Since
                        // we've already seen enough instances of the first "foo" perhaps there is
                        // another element declaration or wildcard deeper in the element map which
                        // matches.
                        return findMatchingDecl(curElem, state, subGroupHandler, elemIndex);
                    }
                }
                else if (state[2] < o.minOccurs) {
                    // not enough loops on the current state.
                    state[1] = state[0];
                    state[0] = XSCMValidator.FIRST_ERROR;
                    return findMatchingDecl(curElem, subGroupHandler);
                }
                else {
                    // Exiting a counting state. If we're entering a new
                    // counting state, reset the counter.
                    o = fCountingStates[nextState];
                    if (o != null) {
                        state[2] = (elemIndex == o.elemIndex) ? 1 : 0;
                    }
                }
            }
            else {
                o = fCountingStates[nextState];
                if (o != null) {
                    // Entering a new counting state. Reset the counter.
                    // If we've already seen one instance of the looping
                    // particle set the counter to 1, otherwise set it
                    // to 0.
                    state[2] = (elemIndex == o.elemIndex) ? 1 : 0;
                }
            }
        }

        state[0] = nextState;
        return matchingDecl;
    }

一句话就是从解析数来的xsd中寻找是否有匹配的标签,如果没有则验证不通过

2016-11-21 17:21:40.648 [localhost-startStop-1] ERROR org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:351) Context initialization failed
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 25 in XML document from file [D:\software\Server\Tomcat\apache-tomcat-7.0.30\webapps\ETeam\WEB-INF\classes\applicationContext-dao.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 25; columnNumber: 51; cvc-complex-type.2.4.a: 发现了以元素 'ref' 开头的无效内容。应以 '{"http://www.springframework.org/schema/beans":description, "http://www.springframework.org/schema/beans":meta, "http://www.springframework.org/schema/beans":constructor-arg, "http://www.springframework.org/schema/beans":property, "http://www.springframework.org/schema/beans":qualifier, "http://www.springframework.org/schema/beans":lookup-method, "http://www.springframework.org/schema/beans":replaced-method, WC[##other:"http://www.springframework.org/schema/beans"]}' 之一开头。
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:399) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:181) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:217) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:125) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:94) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:129) ~[spring-context-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:612) ~[spring-context-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:513) ~[spring-context-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:444) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:326) [spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:107) [spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4791) [catalina.jar:7.0.30]
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5285) [catalina.jar:7.0.30]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:7.0.30]
	at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901) [catalina.jar:7.0.30]
	at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877) [catalina.jar:7.0.30]
	at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:618) [catalina.jar:7.0.30]
	at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1100) [catalina.jar:7.0.30]
	at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1618) [catalina.jar:7.0.30]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [?:1.7.0_79]
	at java.util.concurrent.FutureTask.run(FutureTask.java:262) [?:1.7.0_79]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [?:1.7.0_79]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [?:1.7.0_79]
	at java.lang.Thread.run(Thread.java:745) [?:1.7.0_79]
Caused by: org.xml.sax.SAXParseException: cvc-complex-type.2.4.a: 发现了以元素 'ref' 开头的无效内容。应以 '{"http://www.springframework.org/schema/beans":description, "http://www.springframework.org/schema/beans":meta, "http://www.springframework.org/schema/beans":constructor-arg, "http://www.springframework.org/schema/beans":property, "http://www.springframework.org/schema/beans":qualifier, "http://www.springframework.org/schema/beans":lookup-method, "http://www.springframework.org/schema/beans":replaced-method, WC[##other:"http://www.springframework.org/schema/beans"]}' 之一开头。
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:198) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:134) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:437) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:368) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:325) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:458) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:3237) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:1796) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.emptyElement(XMLSchemaValidator.java:766) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:356) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2786) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:606) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:117) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:848) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:243) ~[?:1.7.0_79]
	at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:347) ~[?:1.7.0_79]
	at org.springframework.beans.factory.xml.DefaultDocumentLoader.loadDocument(DefaultDocumentLoader.java:76) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadDocument(XmlBeanDefinitionReader.java:429) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:391) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	... 26 more


至此令人头疼的xml命名空间就解析完成


四,顺便说下xml的解析

不管是用编程式还是用声明式(ContextLoaderListener或者springMVC DispatcherServlet),都会走到这里:

org.springframework.beans.factory.xml.XmlBeanDefinitionReader.java

/**
	 * 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 {
			Document doc = doLoadDocument(inputSource, resource);
			return registerBeanDefinitions(doc, resource);              //解析xml
		}
		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);
		}
	}


并且默认标签和自定义标签会有不同的执行过程

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.java

/**
	 * 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);
		}
	}


public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
		String namespaceUri = getNamespaceURI(ele);
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}


自定义标签的解析过程大致为:先根据命名空间从META-INF/spring.handlers中获取到handler,如下命名空间:

 <mvc:annotation-driven/>

先根据前缀mvc获取到命名空间http://www.springframework.org/schema/mvc,然后再去META-INF/spring.handlers查找对应的handler:

http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler


MvcNamespaceHandler.java

/*
 * Copyright 2002-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.config;

import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

/**
 * {@link NamespaceHandler} for Spring MVC configuration namespace.
 *
 * @author Keith Donald
 * @author Jeremy Grelle
 * @author Sebastien Deleuze
 * @since 3.0
 */
public class MvcNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
		registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
		registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
		registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
		registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
		registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
	}

}


并调用init方法,注册对应标签的处理器。然后调用parse方法,该方法会根据标签找到对应注册的解析器进行解析,NamespaceHandlerSupport.java

/**
	 * Parses the supplied {@link Element} by delegating to the {@link BeanDefinitionParser} that is
	 * registered for that {@link Element}.
	 */
	@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		return findParserForElement(element, parserContext).parse(element, parserContext);
	}

	/**
	 * Locates the {@link BeanDefinitionParser} from the register implementations using
	 * the local name of the supplied {@link Element}.
	 */
	private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
		String localName = parserContext.getDelegate().getLocalName(element);
		BeanDefinitionParser parser = this.parsers.get(localName);
		if (parser == null) {
			parserContext.getReaderContext().fatal(
					"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
		}
		return parser;
	}

具体"annotation-driven"的解析器就是AnnotationDrivenBeanDefinitionParser


总结:

1)Spring配置文件的解析时基于SAX的,SAX负责根据xsd文件对配置文件进行校验并且生成document类,包括标签命名空间,前缀后缀等,使用的时候都可以从doucument中获取

2)Spring框架对document进行各个标签的解析,其中包含默认标签(命名空间为http://www.springframework.org/schema/beans)和自定义标签的解析分别进行,解析的过程就是把用户在xml中配置好的bean表示成IOC容器的内部结构,这个内部结构就是BeanDefinition,通过注册使容器持有所有的BeanDefinition,也就是放入beanFactory(DefaultListableBeanFactory)的beanDefinitionMap中,这也就是利用beanFactory可以获取bean的原因。总之,spring框架对xml文件的解析(或者是IoC容器的初始化分为三个步骤:Resource定位,载入和注册)

3)Java web程序一般会通过org.springframework.web.context.ContextLoaderListener对配置文件进行解析,而其中会对非懒加载的类进行初始化finishBeanFactoryInitialization(beanFactory);





已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页