最近在研究Spring源代码,在梳理spring-beans时故意只保留org.springframework.beans.factory.xml包下的xsd的3.1版本,然后在看Reference时,上面有一个简单的XML配置文件,如下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- beans --> </beans>
注意到这是一个3.0的xsd,然后测试时发现依然可以正常,只是变慢了,有时甚至卡住了,于是怀疑Spring底层代码访问了网址http://www.springframework.org/schema/beans/spring-beans-3.0.xsd,然后将返回的结果作为校验xml的xsd。
抽取了一部分ClassPathXmlApplicationContext加载过程的代码,仅仅解析XML配置文件,代码如下:
package test.temp;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.xml.DefaultDocumentLoader;
import org.springframework.beans.factory.xml.DelegatingEntityResolver;
import org.springframework.beans.factory.xml.DocumentLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.xml.SimpleSaxErrorHandler;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
public class Test2 {
private static Log logger = LogFactory.getLog(Test2.class);
public static void main(String[] args) throws Exception {
String location = "a/a1.xml";
//
// DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(null);
// XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// int counter = beanDefinitionReader.loadBeanDefinitions(location, null);
//
ResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
for (Resource r : resources) {
EncodedResource encodedResource = new EncodedResource(r);
InputStream inputStream = encodedResource.getResource().getInputStream();
InputSource inputSource = new InputSource(inputStream);
// Resource resource = encodedResource.getResource();
//
// Key 2
EntityResolver entityResolver = new DelegatingEntityResolver(null);
ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
System.out.println("begin");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true); // Key 1
factory.setNamespaceAware(true);
factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
"http://www.w3.org/2001/XMLSchema");
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
System.out.println("waiting...");
DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
System.out.println("builder:[" + docBuilder + "]");
// FIMXE
Document document = docBuilder.parse(inputSource);
/*
DocumentLoader documentLoader = new DefaultDocumentLoader();
Document doc = documentLoader.loadDocument(
inputSource, entityResolver, errorHandler, 3, false);
*/
System.out.println("end");
}
}
}
然后就开始一段痛苦而又折磨人的DEBUG之旅,使用Eclipse,设置端点,Spring使用SAX解析XML,SAX底层代码很复杂,调用极多,所以我就在F5,F6之间迷失了...还好,最终定位到
package com.sun.org.apache.xerces.internal.impl;
...
public class XMLDocumentScannerImpl
extends XMLDocumentFragmentScannerImpl{
...
public void setInputSource(XMLInputSource inputSource) throws IOException {
fEntityManager.setEntityHandler(this);
//this starts a new entity and sets the current entity to the document entity.
fEntityManager.startDocumentEntity(inputSource);
// fDocumentSystemId = fEntityManager.expandSystemId(inputSource.getSystemId());
setScannerState(XMLEvent.START_DOCUMENT);
} // setInputSource(XMLInputSource)
和
package com.sun.org.apache.xerces.internal.impl ;
...
public class XMLEntityManager implements XMLComponent, XMLEntityResolver {
...
public String setupCurrentEntity(String name, XMLInputSource xmlInputSource,
boolean literal, boolean isExternal)
throws IOException, XNIException {
// get information
final String publicId = xmlInputSource.getPublicId();
String literalSystemId = xmlInputSource.getSystemId();
String baseSystemId = xmlInputSource.getBaseSystemId();
String encoding = xmlInputSource.getEncoding();
final boolean encodingExternallySpecified = (encoding != null);
Boolean isBigEndian = null;
// create reader
InputStream stream = null;
Reader reader = xmlInputSource.getCharacterStream();
// First chance checking strict URI
String expandedSystemId = expandSystemId(literalSystemId, baseSystemId, fStrictURI);
if (baseSystemId == null) {
baseSystemId = expandedSystemId;
}
if (reader == null) {
stream = xmlInputSource.getByteStream();
if (stream == null) {
URL location = new URL(expandedSystemId);
URLConnection connect = location.openConnection();
if (!(connect instanceof HttpURLConnection)) {
stream = connect.getInputStream();
}
else {
boolean followRedirects = true;
// setup URLConnection if we have an HTTPInputSource
if (xmlInputSource instanceof HTTPInputSource) {
final HttpURLConnection urlConnection = (HttpURLConnection) connect;
final HTTPInputSource httpInputSource = (HTTPInputSource) xmlInputSource;
// set request properties
Iterator propIter = httpInputSource.getHTTPRequestProperties();
while (propIter.hasNext()) {
Map.Entry entry = (Map.Entry) propIter.next();
urlConnection.setRequestProperty((String) entry.getKey(), (String) entry.getValue());
}
// set preference for redirection
followRedirects = httpInputSource.getFollowHTTPRedirects();
if (!followRedirects) {
setInstanceFollowRedirects(urlConnection, followRedirects);
}
}
stream = connect.getInputStream();
// REVISIT: If the URLConnection has external encoding
// information, we should be reading it here. It's located
// in the charset parameter of Content-Type. -- mrglavas
if (followRedirects) {
String redirect = connect.getURL().toString();
// E43: Check if the URL was redirected, and then
// update literal and expanded system IDs if needed.
if (!redirect.equals(expandedSystemId)) {
literalSystemId = redirect;
expandedSystemId = redirect;
}
}
}
}
...
总算是验证的我的猜想 :)
另外在还在频繁的debug中发现,拔网线会产生异常,但抛出的异常并不是想象中的IOException,而是经过包装后的,乍一看是不知道网络连接失败导致的。
再另,如果classpath下有3.0的xsd,Spring就会直接使用它。