Spring底层使用SAX解析XML配置文件

最近在研究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就会直接使用它。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值