一、背景知识
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);