【Spring源码分析系列】加载Bean

 1 /**
 2      * Create a new XmlBeanFactory with the given input stream,
 3      * which must be parsable using DOM.
 4      * @param resource XML resource to load bean definitions from
 5      * @param parentBeanFactory parent bean factory
 6      * @throws BeansException in case of loading or parsing errors
 7      */
 8     public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
 9         super(parentBeanFactory);
10         this.reader.loadBeanDefinitions(resource);
11     }

整个处理过程:

1)封装资源文件

当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource类进行封装

2)获取输入流

从Resource中获取对应的InputSteam并构造InputSource

3)通过构造的InputSource实例和Resource实例继续调用doLoadBeanDefinitions.

loadBeanDefinition的实现:

 1 /**
 2      * Load bean definitions from the specified XML file.
 3      * @param resource the resource descriptor for the XML file
 4      * @return the number of bean definitions found
 5      * @throws BeanDefinitionStoreException in case of loading or parsing errors
 6      */
 7     @Override
 8     public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
 9         return loadBeanDefinitions(new EncodedResource(resource));
10     }

new EncodeResource(resource)实现:

1 private EncodedResource(Resource resource, String encoding, Charset charset) {
2         super();
3         Assert.notNull(resource, "Resource must not be null");
4         this.resource = resource;
5         this.encoding = encoding;
6         this.charset = charset;
7     }
 1 /**
 2      * Open a {@code java.io.Reader} for the specified resource, using the specified
 3      * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}
 4      * (if any).
 5      * @throws IOException if opening the Reader failed
 6      * @see #requiresReader()
 7      * @see #getInputStream()
 8      */
 9     public Reader getReader() throws IOException {
10         if (this.charset != null) {
11             return new InputStreamReader(this.resource.getInputStream(), this.charset);
12         }
13         else if (this.encoding != null) {
14             return new InputStreamReader(this.resource.getInputStream(), this.encoding);
15         }
16         else {
17             return new InputStreamReader(this.resource.getInputStream());
18         }
19     }

真正的数据准备阶段:

/**
     * Load bean definitions from the specified XML file.
     * @param encodedResource the resource descriptor for the XML file,
     * allowing to specify an encoding to use for parsing the file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     */
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }
        //通过属性值来记录已经加载的资源
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<EncodedResource>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
//从encodeResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputStream InputStream inputStream
= encodedResource.getResource().getInputStream(); try {
//InputResource这个类不来自与Spring 全类路径是org.xml.sax.InputSource InputSource inputSource
= new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); }
//真正的逻辑核心部分
return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }

首先对传入的resource参数做封装,目的是考虑到Resource可能存在的编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions(inputSource,encodeResource.getResource()).

 1 /**
 2      * Actually load bean definitions from the specified XML file.
 3      * @param inputSource the SAX InputSource to read from
 4      * @param resource the resource descriptor for the XML file
 5      * @return the number of bean definitions found
 6      * @throws BeanDefinitionStoreException in case of loading or parsing errors
 7      * @see #doLoadDocument
 8      * @see #registerBeanDefinitions
 9      */
10     protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
11             throws BeanDefinitionStoreException {
12         try {
//加载XML文件,并得到对应的Document
13 Document doc = doLoadDocument(inputSource, resource);
//根据返回的Document注册Bean信息
14 return registerBeanDefinitions(doc, resource); 15 } 16 catch (BeanDefinitionStoreException ex) { 17 throw ex; 18 } 19 catch (SAXParseException ex) { 20 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 21 "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); 22 } 23 catch (SAXException ex) { 24 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 25 "XML document from " + resource + " is invalid", ex); 26 } 27 catch (ParserConfigurationException ex) { 28 throw new BeanDefinitionStoreException(resource.getDescription(), 29 "Parser configuration exception parsing XML from " + resource, ex); 30 } 31 catch (IOException ex) { 32 throw new BeanDefinitionStoreException(resource.getDescription(), 33 "IOException parsing XML document from " + resource, ex); 34 } 35 catch (Throwable ex) { 36 throw new BeanDefinitionStoreException(resource.getDescription(), 37 "Unexpected exception parsing XML document from " + resource, ex); 38 } 39 }

获取XML的验证模式

DTD和XSD区别

DTD即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML和DTD文件来看文档是否符合规范,元素和标签使用是否正确。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。

要使用DTD验证模式的时候需要在XML文件的头部声明,以下是在Spring中使用DTD声明方式的代码:

1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">

获取Document

 1 /**
 2      * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
 3      * XML parser.
 4      */
 5     @Override
 6     public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
 7             ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
 8 
 9         DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
10         if (logger.isDebugEnabled()) {
11             logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
12         }
13         DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
14         return builder.parse(inputSource);
15     }

首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。

在上述方法中涉及一个参数EntityResolver何为EntityResolver?如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例,也就是说,对于解析一个XML,SAX首先读取该XML文档的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个严重。默认的寻找规则,即通过网络来下载相应的DTD声明,并进行认证。

EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可。

解析及注册BeanDefinitions

当把文件转换为Document后,接下来的提取及注册Bean就是我们的重头戏,当程序已经拥有XML文档文件的Document实例对象时,就会被引入如下的方法:

 1 /**
 2      * Register the bean definitions contained in the given DOM document.
 3      * Called by {@code loadBeanDefinitions}.
 4      * <p>Creates a new instance of the parser class and invokes
 5      * {@code registerBeanDefinitions} on it.
 6      * @param doc the DOM document
 7      * @param resource the resource descriptor (for context information)
 8      * @return the number of bean definitions found
 9      * @throws BeanDefinitionStoreException in case of parsing errors
10      * @see #loadBeanDefinitions
11      * @see #setDocumentReaderClass
12      * @see BeanDefinitionDocumentReader#registerBeanDefinitions
13      */
14     public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
15        //使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//记录统计前BeanDefinition的加载个数
16 int countBefore = getRegistry().getBeanDefinitionCount();
//加载及注册bean
17 documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//记录本次加载的BeanDefinition个数
18 return getRegistry().getBeanDefinitionCount() - countBefore; 19 }
 1 /**
 2      * Register each bean definition within the given root {@code <beans/>} element.
 3      */
 4     protected void doRegisterBeanDefinitions(Element root) {
 5         // Any nested <beans> elements will cause recursion in this method. In
 6         // order to propagate and preserve <beans> default-* attributes correctly,
 7         // keep track of the current (parent) delegate, which may be null. Create
 8         // the new (child) delegate with a reference to the parent for fallback purposes,
 9         // then ultimately reset this.delegate back to its original (parent) reference.
10         // this behavior emulates a stack of delegates without actually necessitating one.
11         BeanDefinitionParserDelegate parent = this.delegate;
12         this.delegate = createDelegate(getReaderContext(), root, parent);
13 
14         if (this.delegate.isDefaultNamespace(root)) {
//处理profile属性
15 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); 16 if (StringUtils.hasText(profileSpec)) { 17 String[] specifiedProfiles = StringUtils.tokenizeToStringArray( 18 profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); 19 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { 20 if (logger.isInfoEnabled()) { 21 logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + 22 "] not matching: " + getReaderContext().getResource()); 23 } 24 return; 25 } 26 } 27 } 28 //解析前处理,留给子类实现 29 preProcessXml(root); 30 parseBeanDefinitions(root, this.delegate);
//解析后处理,留给子类实现
31 postProcessXml(root); 32 33 this.delegate = parent; 34 }

preProcessXml和postProcessXml两个方法是空的,这是模板方法模式,在面向对象设计方法中常说:一个雷要么面向集成的设计,要么就用final修饰,这两个方法正是为子类设计的,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些处理的话,那么只需要重写这两个方法就可以了。

解析并注册BeanDefinition:

 1 /**
 2      * Parse the elements at the root level in the document:
 3      * "import", "alias", "bean".
 4      * @param root the DOM root element of the document
 5      */
 6     protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//对beans的处理
7 if (delegate.isDefaultNamespace(root)) { 8 NodeList nl = root.getChildNodes(); 9 for (int i = 0; i < nl.getLength(); i++) { 10 Node node = nl.item(i); 11 if (node instanceof Element) { 12 Element ele = (Element) node; 13 if (delegate.isDefaultNamespace(ele)) {
//对bean的处理
14 parseDefaultElement(ele, delegate); 15 } 16 else {
//对bean的处理
17 delegate.parseCustomElement(ele); 18 } 19 } 20 } 21 } 22 else { 23 delegate.parseCustomElement(root); 24 } 25 }

在Spring的XML配置里面有两大类Bean声明,一个是默认的,如:

1 <bean id="test" class="com.slp.Test"/>

另一类就是自定义的,如:

1 <tx:annotation-driven/>

这两种方式的读取及解析差别是非常大的,如果采用Spring默认的配置,Spring当然知道该如何做,但是如果是自定义的,那么久需要用户实现一些接口及配置了。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCutomElement方法对自定义命名空间进行解析。而判断是否默认命名空间还是自定义命名空间的方法其实是使用node.getNamespaceURI()获取命名空间,并与Spring的命名空间进行对比,如果一致则是默认,否则是自定义。

原文地址:https://www.cnblogs.com/dream-to-pku/p/7591692.html