《Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实现

Spring的整体架构

Spring框架是一个分层架构,它包含一系列的功能要素,并被分为大约20个模块,如下图所示

《《Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实现》

这些模块被总结为以下几个部分:

  • Core Container

    Core Container(核心容器)包含有Core、Beans、Context和Expression Language模块
    Core和Beans模块是框架的基础部分,提供IoC(转控制)和依赖注入特性。这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置

    • Core模块主要包含Spring框架基本的核心工具类
    • Beans模块是所有应用都要用到的,它包含访问配置文件、创建和管理bean以及进行Inversion of Control/Dependency Injection(Ioc/DI)操作相关的所有类
    • Context模块构建于Core和Beans模块基础之上,提供了一种类似于JNDI注册器的框架式的对象访问方法。Context模块继承了Beans的特性,为Spring核心提供了大量扩展,添加了对国际化(如资源绑定)、事件传播、资源加载和对Context的透明创建的支持。ApplicationContext接口是Context模块的关键
    • Expression Language模块提供了一个强大的表达式语言用于在运行时查询和操纵对象,该语言支持设置/获取属性的值,属性的分配,方法的调用,访问数组上下文、容器和索引器、逻辑和算术运算符、命名变量以及从Spring的IoC容器中根据名称检索对象
  • Data Access/Integration

    • JDBC模块提供了一个JDBC抽象层,它可以消除冗长的JDBC编码和解析数据库厂商特有的错误代码,这个模块包含了Spring对JDBC数据访问进行封装的所有类
    • ORM模块为流行的对象-关系映射API,如JPA、JDO、Hibernate、iBatis等,提供了一个交互层,利用ORM封装包,可以混合使用所有Spring提供的特性进行O/R映射,如前边提到的简单声明性事务管理
  • Web

    Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文,所以Spring框架支持与Jakarta Struts的集成。Web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。Web层包含了Web、Web-Servlet、Web-Struts和Web、Porlet模块

    • Web模块:提供了基础的面向Web的集成特性,例如,多文件上传、使用Servlet listeners初始化IoC容器以及一个面向Web的应用上下文,它还包含了Spring远程支持中Web的相关部分
    • Web-Servlet模块web.servlet.jar:该模块包含Spring的model-view-controller(MVC)实现,Spring的MVC框架使得模型范围内的代码和web forms之间能够清楚地分离开来,并与Spring框架的其他特性基础在一起
    • Web-Struts模块:该模块提供了对Struts的支持,使得类在Spring应用中能够与一个典型的Struts Web层集成在一起
    • Web-Porlet模块:提供了用于Portlet环境和Web-Servlet模块的MVC的实现
  • AOP

    AOP模块提供了一个符合AOP联盟标准的面向切面编程的实现,它让你可以定义例如方法拦截器和切点,从而将逻辑代码分开,降低它们之间的耦合性,利用source-level的元数据功能,还可以将各种行为信息合并到你的代码中

    Spring AOP模块为基于Spring的应用程序中的对象提供了事务管理服务,通过使用Spring AOP,不用依赖EJB组件,就可以将声明性事务管理集成到应用程序中

  • Test

    Test模块支持使用Junit和TestNG对Spring组件进行测试

容器的基本实现

Spring的结构组成

beans包的层级结构

beans包中的各个源码包的功能如下

  • src/main/java 用于展现Spring的主要逻辑
  • src/main/resources 用于存放系统的配置文件
  • src/test/java 用于对主要逻辑进行单元测试
  • src/test/resources 用于存放测试用的配置文件

核心类介绍

1.DefaultListableBeanFactory

XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现,而对于XmlBeanFactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory并实现了ConfigURableListableBeanFactory以及BeanDefinitionRegistry接口。以下是ConfigURationListableBeanFactory的层次结构图以下相关类图

《《Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实现》

容器加载相关类图

《《Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实现》

类图中各个类的作用:

  • AliasRegistry:定义对alias的简单增删改等操作
  • SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现
  • SingletonBeanRegistry:定义对单例的注册及获取
  • BeanFactory:定义获取bean及bean的各种属性
  • DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry各函数的实现
  • HierarchicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持
  • BeanDefinitionRegistry:定义对BeanDefinition的各种增删改操作
  • FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能
  • ConfigurableBeanFactory:提供配置Factory的各种方法
  • ListableBeanFactory:根据各种条件获取bean的配置清单
  • AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurationBeanFactory的功能
  • AutowireCapableBeanFactory:提供创建bean、自动注入、初始化以及应用bean的后处理器
  • AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapableBeanFactory进行实现
  • ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等
  • DefaultListableBeanFactory:综合上面所有功能,主要是对Bean注册后的处理

XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取Bean都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性。在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册

2.XmlBeanDefinitionReader

XML配置文件的读取是Spring中重要的功能,因为Spring的大部分功能都是以配置作为切入点的,可以从XmlBeanDefinitionReader中梳理一下资源文件读取、解析及注册的大致脉络,首先看看各个类的功能

  • ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource
  • BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能
  • EnvironmentCapable:定义获取Environment方法
  • DocumentLoader:定义从资源文件加载到转换为Document的功能
  • AbstractBeanDefinitionReader:对EnvironmentCapable、BeanDefinitionReader类定义的功能进行实现
  • BeanDefinitionDocumentReader:定义读取Document并注册BeanDefinition功能
  • BeanDefinitionParserDelegate:定义解析Element的各种方法

整个XML配置文件读取的大致流程,在XmlBeanDefinitionReader中主要包含以下几步处理

《《Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实现》

(1)通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourceLoader将资源文件路径转换为对应的Resource文件

(2)通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Document文件

(3)通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析

容器的基础XmlBeanFactory

BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));

通过XmlBeanFactory初始化时序图看一看上面代码的执行逻辑

《《Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实现》

时序图从BeanFactoryTest测试类开始,首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续的资源处理就可以用Resource提供的各种服务来操作了

配置文件封装

Spring的配置文件读取是通过ClassPathResource进行封装的,Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口来封装底层资源

public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
    boolean exists();

    boolean isReadable();

    boolean isOpen();

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String var1) throws IOException;

    String getFilename();

    String getDescription();
}

InputStreamSource封装任何能返回InputStream的类,比如File、Classpath下的资源和Byte Array等, 它只有一个方法定义:getInputStream(),该方法返回一个新的InputStream对象

Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等。首先,它定义了3个判断当前资源状态的方法:存在性(exists)、可读性(isReadable)、是否处于打开状态(isOpen)。另外,Resource接口还提供了不同资源到URL、URI、File类型的转换,以及获取lastModified属性、文件名(不带路径信息的文件名,getFilename())的方法,为了便于操作,Resource还提供了基于当前资源创建一个相对资源的方法:createRelative(),还提供了getDescription()方法用于在错误处理中的打印信息

对不同来源的资源文件都有相应的Resource实现:文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等,相关类图如下所示

《《Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实现》

当通过Resource相关类完成了对配置文件进行封装后,配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理了

XmlBeanFactory的初始化有若干办法,Spring提供了很多的构造函数,在这里分析的是使用Resource实例作为构造函数参数的办法,代码如下

public XmlBeanFactory(Resource resource) throws BeansException {
    //调用XmlBeanFactory(Resource,BeanFactory)构造方法
    this(resource, (BeanFactory)null);
}

//parentBeanFactory为父类BeanFactory用于factory合并,可以为空
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    this.reader = new XmlBeanDefinitionReader(this);
    this.reader.loadBeanDefinitions(resource);
}

上面函数中的代码this.reader.loadBeanDefinitions(resource)才是资源加载的真正实现,时序图中提到的XmlBeanDefinitionReader加载数据就是在这里完成的,但是在XmlBeanDefinitionReader加载数据前还有一个调用父类构造函数初始化的过程:super(parentBeanFactory),跟踪代码到父类AbstractAutowireCapableBeanFactory的构造函数中:

public AbstractAutowireCapableBeanFactory() {
    super();
    this.ignoreDependencyInterface(BeanNameAware.class);
    this.ignoreDependencyInterface(BeanFactoryAware.class);
    this.ignoreDependencyInterface(BeanClassLoaderAware.class);
}

ignoreDependencyInterface的主要功能是忽略给定接口的自动装配功能,目的是:实现了BeanNameAware接口的属性,不会被Spring自动初始化。自动装配时忽略给定的依赖接口,典型应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware进行注入

加载Bean

在XmlBeanFactory构造函数中调用了XmlBeanDefinitionReader类型的reader属性提供的方法this.reader.loadBeanDefinitions(resource),而这句代码则是整个资源加载的切入点,这个方法的时序图如下

《《Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实现》

(1)封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装
(2)获取输入流。从Resource中获取对应的InputStream并构造InputSource
(3)通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions,loadBeanDefinitions函数具体的实现过程:

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return this.loadBeanDefinitions(new EncodedResource(resource));
}

EncodedResource的作用是对资源文件的编码进行处理的,其中的主要逻辑体现在getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码

public Reader getReader() throws IOException {
    return this.encoding != null?new InputStreamReader(this.resource.getInputStream(), this.encoding):new InputStreamReader(this.resource.getInputStream());
}

在构造好了encodeResource对象后,再次转入了可复用方法loadBeanDefinitions(new EncodedResource(resource)),这个方法内部才是真正的数据准备阶段

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if(this.logger.isInfoEnabled()) {
        this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
    }

    //通过属性来记录已经加载的资源
    Object currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
    if(currentResources == null) {
        currentResources = new HashSet(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }

    if(!((Set)currentResources).add(encodedResource)) {
        throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    } else {
        int var6;
        try {
            //从encodedResource中获取已经封装的Resource对象并再次从Resource中获取其中的InputStream
            InputStream ex = encodedResource.getResource().getInputStream();

            try {
                InputSource inputSource = new InputSource(ex);
                if(encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }

                //真正进入了逻辑核心部分
                var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            } finally {
                ex.close();
            }
        } catch (IOException var15) {
            throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
        } finally {
            ((Set)currentResources).remove(encodedResource);
            if(((Set)currentResources).isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.set((Object)null);
            }

        }

        return var6;
    }
}

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

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
    try {
        int ex = this.getValidationModeForResource(resource);
        Document doc = this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, ex, this.isNamespaceAware());
        return this.registerBeanDefinitions(doc, resource);
    } catch (BeanDefinitionStoreException var5) {
        throw var5;
    } catch (SAXParseException var6) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6);
    } catch (SAXException var7) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7);
    } catch (ParserConfigurationException var8) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8);
    } catch (IOException var9) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9);
    } catch (Throwable var10) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10);
    }
}

在上面冗长的代码中假如不考虑异常类代码,其实只做了三件事

  • 获取对XML文件的验证模式
  • 加载XML文件,并得到对应的Document
  • 根据返回的Document注册Bean信息

获取XML的验证模式

XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有两种:DTD和XSD

DTD和XSD区别

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

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

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

而以Spring为例,具体的Spring-beans-2.0.dtd部分如下:

<!ELEMENT beans (
    description?,
    (import | alias | bean)*
)>
<!ATTLIST beans default-lazy-init (true | false) "false">
<!ATTLIST beans default-merge (true | false) "false">
<!ATTLIST beans default-autowire (no | byName | byType | constructor | autodetect) "no">
<!ATTLIST beans default-dependency-check (none | objects | simple | all) "none">
<!ATTLIST beans default-init-method CDATA #IMPLIED>
<!ATTLIST beans default-destroy-method CDATA #IMPLIED>
.....

XML Schema语言就是XSD(XML Schemas Definition)。XML Schema描述了XML文档的结构,可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其要求,文档设计者可以通过XML Schema指定一个XML文档所允许的结构和内容,并可据此检查一个XML文档是否是有效的

在使用XML Schema文档对XML实例文档进行检验,除了要声明名称空间外(xmlns=http://www.Springframework.org/schema/beans),还必须指定该名称空间所对应的XML Schema文档的存储位置,通过schemaLocation属性来指定名称空间所对应的XML Schema文档的存储位置,它包含两个部分,一部分是名称空间的URI,另一部分就该名称空间所标识的XML Schema文件位置或URL地址(xsi:schemaLocation=”http://www.Springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans.xsd“)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.Springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema/beans"
    xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans.xsd">
....
</beans>

Spring-beans-3.0.xsd部分代码如下:

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

<xsd:schema xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.springframework.org/schema/beans">

    <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>

    <xsd:annotation>
        <xsd:documentation><![CDATA[
        ....
        ]]></xsd:documentation>
    </xsd:annotation>

    <!-- base types -->
    <xsd:complexType name="identifiedType" abstract="true">
        <xsd:annotation>
            <xsd:documentation><![CDATA[
    The unique identifier for a bean. The scope of the identifier
    is the enclosing bean factory.
            ]]></xsd:documentation>
        </xsd:annotation>
        <xsd:attribute name="id" type="xsd:ID">
            <xsd:annotation>
                <xsd:documentation><![CDATA[
    The unique identifier for a bean.
                ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
    </xsd:complexType>
    ......
</xsd:schema>

验证模式的读取

Spring通过getValidationModeForResource方法来获取对应资源的验证模式

protected int getValidationModeForResource(Resource resource) {
    int validationModeToUse = this.getValidationMode();
    //如果手动指定了验证模式则使用指定的验证模式
    if(validationModeToUse != 1) {
        return validationModeToUse;
    } else {
        //如果未指定则使用自动检测
        int detectedMode = this.detectValidationMode(resource);
        return detectedMode != 1?detectedMode:3;
    }
}

自动检测验证模式的功能是在函数detectValidationMode方法中实现的,在detectValidationMode函数中又将自动检测验证模式的工作委托给了专门处理类XmlValidationModeDetector的detectValidationMode方法

protected int detectValidationMode(Resource resource) {
    if(resource.isOpen()) {
        throw new BeanDefinitionStoreException("Passed-in Resource [" + resource + "] contains an open stream: " + "cannot determine validation mode automatically. Either pass in a Resource " + "that is able to create fresh streams, or explicitly specify the validationMode " + "on your XmlBeanDefinitionReader instance.");
    } else {
        InputStream inputStream;
        try {
            inputStream = resource.getInputStream();
        } catch (IOException var5) {
            throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " + "Did you attempt to load directly from a SAX InputSource without specifying the " + "validationMode on your XmlBeanDefinitionReader instance?", var5);
        }

        try {
            return this.validationModeDetector.detectValidationMode(inputStream);
        } catch (IOException var4) {
            throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", var4);
        }
    }
}

XmlValidationModeDetector类中的detectValidationMode方法如下

public int detectValidationMode(InputStream inputStream) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

    try {
        boolean isDtdValidated = false;

        while(true) {
            String content;
            if((content = reader.readLine()) != null) {
                content = this.consumeCommentTokens(content);
                //如果读取的行是空或者注释则略过
                if(this.inComment || !StringUtils.hasText(content)) {
                    continue;
                }

                if(this.hasDoctype(content)) {
                    isDtdValidated = true;
                } else if(!this.hasOpeningTag(content)) {//读取到<开始符号,验证模式一定会会在开始符号之前
                    continue;
                }
            }

            int var6 = isDtdValidated?2:3;
            return var6;
        }
    } catch (CharConversionException var9) {
        ;
    } finally {
        reader.close();
    }

    return 1;
}

Spring用来检测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD

获取Document

经过了验证模式准备的步骤就可以进行Document加载了,对于文档的读取委托给了DocumentLoader去执行,这里的DocumentLoader是个接口,而真正调用的是DefaultDocumentLoader,解析代码如下

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
    if(logger.isDebugEnabled()) {
        logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
    }

    DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource);
}

首选创建DocumentBuildFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析InputSource来返回Document对象。对于参数entityResolver,传入的是通过getEntityResolver()函数获取的返回值,代码如下

protected EntityResolver getEntityResolver() {
    if(this.entityResolver == null) {
        ResourceLoader resourceLoader = this.getResourceLoader();
        if(resourceLoader != null) {
            this.entityResolver = new ResourceEntityResolver(resourceLoader);
        } else {
            this.entityResolver = new DelegatingEntityResolver(this.getBeanClassLoader());
        }
    }

    return this.entityResolver;
}

EntityResolver用法

如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例。也就是说,对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证,默认的寻找规则,即通过网络(实现上就是声明DTD的URI地址)来下载相应的DTD声明,并进行认证。下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里会报错,就是因为相应的DTD声明没有被找到的原因

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

InputSource resolveEntity(String publicId, String systemId)

这里,它接收两个参数publicId和systemId,并返回一个InputSource对象,以特定配置文件来进行讲解

(1)如果在解析验证模式为XSD的配置文件,代码如下

<?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.xsd">
....
</beans>

读取到以下两个参数

(2)如果解析验证模式为DTD的配置文件,代码如下:

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

读取到以下两个参数

一般都会把验证文件放置在自己的工程里,如果把URL转换为自己工程里对应的地址文件呢?以加载DTD文件为例来看看Spring是如何实现的。根据之前Spring中通过getEntityResolver()方法对EntityResolver的获取,我们知道,Spring中使用DelegatingEntityResolver类为EntityResolver的实现类,resolveEntity实现方法如下:

//DelegatingEntityResolver.java

    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
    if(systemId != null) {
        if(systemId.endsWith(".dtd")) {
            //dtd从这里解析
            return this.dtdResolver.resolveEntity(publicId, systemId);
        }

        if(systemId.endsWith(".xsd")) {
            //通过调用META-INF/Spring.schemas解析
            return this.schemaResolver.resolveEntity(publicId, systemId);
        }
    }

    return null;
}

对不同的验证模式,Spring使用了不同的解析器解析,比如加载DTD类型的BeansDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去当前路径下寻找,而加载XSD类型的PluggableSchemaResolver类的resolveEntity是默认到META-INF/Spring.schemas文件中找到systemId所对应的XSD文件并加载

//BeansDtdResolver.java

    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 && systemId.endsWith(".dtd")) {
        int lastPathSeparator = systemId.lastIndexOf("/");
        String[] var7 = DTD_NAMES;
        int var6 = DTD_NAMES.length;

        for(int var5 = 0; var5 < var6; ++var5) {
            String DTD_NAME = var7[var5];
            // DTD_NAMES = {"Spring-beans-2.0", "Spring-beans"}
            int dtdNameStart = systemId.indexOf(DTD_NAME);
            if(dtdNameStart > lastPathSeparator) {
                String dtdFile = systemId.substring(dtdNameStart);
                if(logger.isTraceEnabled()) {
                    logger.trace("Trying to locate [" + dtdFile + "] in Spring jar");
                }

                try {
                    ClassPathResource ex = new ClassPathResource(dtdFile, this.getClass());
                    InputSource source = new InputSource(ex.getInputStream());
                    source.setPublicId(publicId);
                    source.setSystemId(systemId);
                    if(logger.isDebugEnabled()) {
                        logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
                    }

                    return source;
                } catch (IOException var12) {
                    if(logger.isDebugEnabled()) {
                        logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in class path", var12);
                    }
                }
            }
        }
    }

    return null;
}

解析并注册BeanDefinition

当把文件转换成Document后,接下来就是对bean的提取及注册,当程序已经拥有了XML文档文件的Document实例对象时,就会被引入下面这个方法

//XmlBeanDefinitionReader.java

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    //使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
    BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
    //记录统计之前BeanDefinition的加载个数
    int countBefore = this.getRegistry().getBeanDefinitionCount();
    //加载及注册bean
    documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
    //记录本次加载的BeanDefinition个数
    return this.getRegistry().getBeanDefinitionCount() - countBefore;
}

BeanDefinitionDocumentReader是一个接口,而实例化的工作是在createBeanDefinitionDocumentReader()中完成的,而通过此方法,BeanDefinitionDocumentReader真正的类型其实已经是DefaultBeanDefinitionDocumentReader了,进入DefaultBeanDefinitionDocumentReader后,发现这个方法的重要目的之一就是提取root,以便于再次将root作为参数继续BeanDefinition的注册

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    this.logger.debug("Loading bean definitions");
    Element root = doc.getDocumentElement();
    BeanDefinitionParserDelegate delegate = this.createHelper(readerContext, root);
    //解析前处理,留给子类实现
    this.preProcessXml(root);
    this.parseBeanDefinitions(root, delegate);
    //解析后处理,留给子类实现
    this.postProcessXml(root);
}

解析并注册BeanDefinition

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    //对beans的处理
    if(delegate.isDefaultNamespace(delegate.getNamespaceURI(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;
                String namespaceUri = delegate.getNamespaceURI(ele);
                if(delegate.isDefaultNamespace(namespaceUri)) {
                    //对bean的处理
                    this.parseDefaultElement(ele, delegate);
                } else {
                    //对bean的处理
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        delegate.parseCustomElement(root);
    }

}

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

<bean id="test" class="test.TestBean"/>

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

<tx:annotation-driven>

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

    原文作者:Spring MVC
    原文地址: https://blog.csdn.net/benhuo931115/article/details/74611464
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞