您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

spring源码深度解析—容器的基本实现,你知多少?

时间:2019-12-13 10:48:22  来源:  作者:

1. 概述

分析源码是一件非常具有挑战性的工作,在正是分析spring的源码之前我们先来简单回顾下spring核心功能的简单使用

2. 容器的基本用法

bean是spring最核心的东西,spring就像是一个大水桶,而bean就是水桶中的水,水桶脱离了水也就没有什么用处了,我们简单看下bean的定义,代码如下:

public class MyBeanDemo {
 private String beanName = "bean";

 public String getBeanName() {
 return beanName;
 }
 public void setBeanName(String beanName) {
 this.beanName = beanName;
 }
}

源码很简单,bean没有特别之处,spring的的目的就是让我们的bean成为一个纯粹的的POJO,这就是spring追求的,接下来就是在配置文件中定义这个bean,配置文件如下:

<?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">

 <bean id="demo" class="com.yhl.myspring.demo.bean.MyBeanDemo">
 <property name="beanName" value="bean demo1"/>
 </bean>

</beans>

在上面的配置中我们可以看到bean的声明方式,在spring中的bean定义有N中属性,但是我们只要像上面这样简单的声明就可以使用了。 
具体测试代码如下:

public class TestDemo {
 public static void main(String[] args) {
 BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-bean.xml"));

 try {
 MyBeanDemo bean = (MyBeanDemo)factory.getBean("demo");
 System.out.println(bean.getBeanName());
 } catch (BeansException e) {
 e.printStackTrace();
 }
 }
}

运行上述测试代码就可以看到输出结果如下图: 

spring源码深度解析—容器的基本实现,你知多少?

 


其实直接使用BeanFactory作为容器对于Spring的使用并不多见,因为企业级应用项目中大多会使用的是ApplicationContext(后面我们会讲两者的区别,这里只是测试)

3. 功能分析

接下来我们分析2中代码完成的功能; 
- 读取配置文件spring-bean.xml。 
- 根据spring-beanxml中的配置找到对应的类的配置,并实例化。 
- 调用实例化后的实例 
下图是一个最简单spring功能架构,如果想完成我们预想的功能,至少需要3个类: 

spring源码深度解析—容器的基本实现,你知多少?

 


其中, 
ConfigReader:用于读取及验证配置文件。我们要用配置文件里面的东西,当然首先要做的就是读取,然后放置在内存中。 
ReflectionUtil:用于根据配置文件中的配置进行反射实例化。比如在例2.1中spring-bean.xml出现的 
我们就可以根据bean.demo进行实例化。 
APP:用于完成整个逻辑的串联。

4. 工程搭建

在spring的源码中用于实现上面功能的是spring-bean这个工程,所以我们接下来看这个工程,当然spring-core是必须的。

4.1 beans包的层级结构

阅读源码最好的方式是跟着示例操作一遍,我们先看看beans工程的源码结构,如下图所示: 

spring源码深度解析—容器的基本实现,你知多少?

 


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

4.2 核心类介绍

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

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属性对资源文件进行读取和注册 
4.2.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源码深度解析—容器的基本实现,你知多少?

 


(1)通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourceLoader将资源文件路径转换为对应的Resource文件 
(2)通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Document文件 
(3)通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析

5. 容器的基础XmlBeanFactory

通过上面的内容我们对spring的容器已经有了大致的了解,接下来我们详细探索每个步骤的详细实现,接下来要分析的功能都是基于如下代码:

BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-bean.xml"));

通过XmlBeanFactory初始化时序图看一看上面代码的执行逻辑,如下图所示: 

spring源码深度解析—容器的基本实现,你知多少?

 


时序图从TestDemo测试类开始,首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续的资源处理就可以用Resource提供的各种服务来操作了。有了Resource后就可以对BeanFactory进行初始化操作,那配置文件是如何封装的呢? 
5.1 配置文件的封装 
Spring的配置文件读取是通过ClassPathResource进行封装的,Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口来封装底层资源,如下源码:

public interface InputStreamSource {
 InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
 boolean exists();
 default boolean isReadable() {
 return true;
 }
 default boolean isOpen() {
 return false;
 }
 default boolean isFile() {
 return false;
 }
 URL getURL() throws IOException;
 URI getURI() throws IOException;
 File getFile() throws IOException;
 default ReadableByteChannel readableChannel() throws IOException {
 return Channels.newChannel(getInputStream());
 }
 long contentLength() throws IOException;
 long lastModified() throws IOException;
 Resource createRelative(String relativePath) 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 resource = new ClassPathResource("spring-bean.xml");
InputStream is = resource.getInputStream();

当通过Resource相关类完成了对配置文件进行封装后,配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理了。 
接下来就进入到XmlBeanFactory的初始化过程了,XmlBeanFactory的初始化有若干办法,Spring提供了很多的构造函数,在这里分析的是使用Resource实例作为构造函数参数的办法,代码如下:

 public XmlBeanFactory(Resource resource) throws BeansException {
 this(resource, null);
 }
 public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
 super(parentBeanFactory);
 this.reader.loadBeanDefinitions(resource);
}

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

public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
 super(parentBeanFactory);
}

然后继续跟踪,跟踪代码到父类AbstractAutowireCapableBeanFactory的构造函数中:

public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
 this();
 setParentBeanFactory(parentBeanFactory);
}
public AbstractAutowireCapableBeanFactory() {
 super();
 ignoreDependencyInterface(BeanNameAware.class);
 ignoreDependencyInterface(BeanFactoryAware.class);
 ignoreDependencyInterface(BeanClassLoaderAware.class);
}

这里要提及一下ignoreDependencyInterface方法,此方法的主要功能是忽略给定接口的自动装配功能,目的是:实现了BeanNameAware接口的属性,不会被Spring自动初始化。自动装配时忽略给定的依赖接口,典型应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware进行注入。 
5.2 bean加载 
在之前XmlBeanFactory构造函数中调用了XmlBeanDefinitionReader类型的reader属性提供的方法this.reader.loadBeanDefinitions(resource),而这句代码则是整个资源加载的切入点,这个方法的时序图如下: 

spring源码深度解析—容器的基本实现,你知多少?

 


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

spring源码深度解析—容器的基本实现,你知多少?

 


EncodedResource的作用是对资源文件的编码进行处理的,其中的主要逻辑体现在getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码,在构造好了encodeResource对象后,再次转入了可复用方法loadBeanDefinitions(new EncodedResource(resource)),这个方法内部才是真正的数据准备阶段,代码如下:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
 throws BeanDefinitionStoreException {
 try {
 Document doc = doLoadDocument(inputSource, resource);
 return registerBeanDefinitions(doc, resource);
 }
 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);
 }
}

在上面冗长的代码中假如不考虑异常类代码,其实只做了三件事 
- 获取对XML文件的验证模式 
- 加载XML文件,并得到对应的Document 
- 根据返回的Document注册Bean信息 
5.3 获取XML的验证模式 
XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有两种:DTD和XSD 
5.3.1 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)*
)>
<!--
 Default values for all bean definitions. Can be overridden at
 the "bean" level. See those attribute definitions for details.
-->
<!ATTLIST beans default-lazy-init (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>
<!ATTLIST beans default-merge (true | false) "false">
<!--
 Element containing informative text describing the purpose of the enclosing
 element. Always optional.
 Used primarily for user documentation of XML bean definition documents.
-->
<!ELEMENT description (#PCDATA)>
<!--
 Specifies an XML bean definition resource to import.
-->
<!ELEMENT import EMPTY>

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/beanshttp://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-instance"
 xsi:schemaLocation="
 http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd">

 <bean id="demo" class="com.yhl.myspring.demo.bean.MyBeanDemo">
 <property name="beanName" value="bean demo1"/>
 </bean>
</beans>

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

spring源码深度解析—容器的基本实现,你知多少?

 


5.3.2 验证模式的读取 
在spring中,是通过getValidationModeForResource方法来获取对应资源的验证模式,其源码如下:

protected int getValidationModeForResource(Resource resource) {
 int validationModeToUse = getValidationMode();
 if (validationModeToUse != VALIDATION_AUTO) {
 return validationModeToUse;
 }
 int detectedMode = detectValidationMode(resource);
 if (detectedMode != VALIDATION_AUTO) {
 return detectedMode;
 }
 // Hmm, we didn't get a clear indication... Let's assume XSD,
 // since apparently no DTD declaration has been found up until
 // detection stopped (before finding the document's root tag).
 return VALIDATION_XSD;
}

方法的实现还是很简单的,如果设定了验证模式则使用设定的验证模式(可以通过使用XmlBeanDefinitionReader中的setValidationMode方法进行设定),否则使用自动检测的方式。而自动检测验证模式的功能是在函数detectValidationMode方法中,而在此方法中又将自动检测验证模式的工作委托给了专门处理类XmlValidationModeDetector的validationModeDetector方法,具体代码如下:

public int detectValidationMode(InputStream inputStream) throws IOException {
 // Peek into the file to look for DOCTYPE.
 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
 try {
 boolean isDtdValidated = false;
 String content;
 while ((content = reader.readLine()) != null) {
 content = consumeCommentTokens(content);
 if (this.inComment || !StringUtils.hasText(content)) {
 continue;
 }
 if (hasDoctype(content)) {
 isDtdValidated = true;
 break;
 }
 if (hasOpeningTag(content)) {
 // End of meaningful data...
 break;
 }
 }
 return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
 }
 catch (CharConversionException ex) {
 // Choked on some character encoding...
 // Leave the decision up to the caller.
 return VALIDATION_AUTO;
 }
 finally {
 reader.close();
 }
 }

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

5.4. 获取Document

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

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

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

protected EntityResolver getEntityResolver() {
 if (this.entityResolver == null) {
 // Determine default EntityResolver to use.
 ResourceLoader resourceLoader = getResourceLoader();
 if (resourceLoader != null) {
 this.entityResolver = new ResourceEntityResolver(resourceLoader);
 }
 else {
 this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
 }
 }
 return this.entityResolver;
}

这个entityResolver是做什么用的呢,接下来我们详细分析下。 
5.4.1 EntityResolver 的用法 
如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例。也就是说,对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证,默认的寻找规则,即通过网络(实现上就是声明DTD的URI地址)来下载相应的DTD声明,并进行认证。下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里会报错,就是因为相应的DTD声明没有被找到的原因 
EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可,在EntityResolver的接口只有一个方法声明:

public abstract InputSource resolveEntity (String publicId, String systemId)
 throws SAXException, IOException;

它接收两个参数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>

则会读取到以下两个参数 
- publicId:null 
- systemId:http://www.Springframework.org/schema/beans/Spring-beans.xsd 
(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>

读取到以下两个参数 
- publicId:-//Spring//DTD BEAN 2.0//EN 
- systemId:http://www.Springframework.org/dtd/Spring-beans-2.0.dtd 
一般都会把验证文件放置在自己的工程里,如果把URL转换为自己工程里对应的地址文件呢?以加载DTD文件为例来看看Spring是如何实现的。根据之前Spring中通过getEntityResolver()方法对EntityResolver的获取,我们知道,Spring中使用DelegatingEntityResolver类为EntityResolver的实现类,resolveEntity实现方法如下:

@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable 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;

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

@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable 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_EXTENSION)) {
 int lastPathSeparator = systemId.lastIndexOf('/');
 int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
 if (dtdNameStart != -1) {
 String dtdFile = DTD_NAME + DTD_EXTENSION;
 if (logger.isTraceEnabled()) {
 logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
 }
 try {
 Resource resource = new ClassPathResource(dtdFile, getClass());
 InputSource source = new InputSource(resource.getInputStream());
 source.setPublicId(publicId);
 source.setSystemId(systemId);
 if (logger.isDebugEnabled()) {
 logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
 }
 return source;
 }
 catch (IOException ex) {
 if (logger.isDebugEnabled()) {
 logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
 }
 }

 }
 }

 // Use the default behavior -> download from website or wherever.
 return null;
}

5.5 解析及注册BeanDefinitions

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

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
 int countBefore = getRegistry().getBeanDefinitionCount();
 documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
 return getRegistry().getBeanDefinitionCount() - countBefore;
}

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

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
 this.readerContext = readerContext;
 logger.debug("Loading bean definitions");
 Element root = doc.getDocumentElement();
 doRegisterBeanDefinitions(root);
}

通过这里我们看到终于到了解析逻辑的核心方法doRegisterBeanDefinitions,接着跟踪源码如下:

protected void doRegisterBeanDefinitions(Element root) {
 BeanDefinitionParserDelegate parent = this.delegate;
 this.delegate = createDelegate(getReaderContext(), root, parent);
 if (this.delegate.isDefaultNamespace(root)) {
 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
 if (StringUtils.hasText(profileSpec)) {
 String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
 profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
 if (logger.isInfoEnabled()) {
 logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
 "] not matching: " + getReaderContext().getResource());
 }
 return;
 }
 }
 }
 preProcessXml(root);
 parseBeanDefinitions(root, this.delegate);
 postProcessXml(root);
 this.delegate = parent;
 }12345678910111213141516171819202122

我们看到首先要解析profile属性,然后才开始XML的读取,具体的代码如下:

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);
 }
 }

在Spring的XML配置里面有两大类Bean声明,一个是默认的,如: 
另一类就是自定义的,如: 
而这两种方式的读取及解析差别是非常大的,如果采用Spring默认的配置,Spring当然知道该怎么做,但如果是自定义的,那么就需要用户实现一些接口及配置了。对于根节点或子节点如果是默认命名空间的话采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement方法对自定义命名空间进行解析。而判断是否默认命名空间还是自定义命名空间的办法其实是使用node.getNamespaceURI()获取命名空间,并与Spring中固定的命名空间http://www.springframework.org/schema/beans进行对比,如果一致则认为是默认,否则就认为是自定义。 
profile的用法 
通过profile标记不同的环境,可以通过设置spring.profiles.active和spring.profiles.default激活指定profile环境。如果设置了active,default便失去了作用。如果两个都没有设置,那么带有profiles的bean都不会生成。 
有多种方式来设置这两个属性:

作为DispatcherServlet的初始化参数;
作为web应用的上下文参数;
作为JNDI条目;
作为环境变量; System.set("spring.profiles.active","prod")
作为JVM的系统属性; -Dspring.profiles.active="prod"
在集成测试类上,使用@ActiveProfiles注解配置。

以前两种方式举例,它们都可以在web.xml中设置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
 xmlns="http://java.sun.com/xml/ns/javaee" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
 http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
 <display-name></display-name> 
 <welcome-file-list>
 <welcome-file>index.jsp</welcome-file>
 </welcome-file-list>
 <context-param>
 <param-name>applicationContext</param-name>
 <param-value>/applicationContext.xml</param-value>
 </context-param>
 <!-- 在上下文中设置profile的默认值 -->
 <context-param>
 <param-name>spring.profiles.default</param-name>
 <param-value>dev</param-value>
 </context-param>

 <listener>
 <listener-class>
 org.springframework.web.context.ContextLoaderListener
 </listener-class>
 </listener>

 <servlet>
 <servlet-name>appServlet</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 <!-- 在servlet中设置profile的默认值 -->
 <init-param>
 <param-name>spring.profiles.default</param-name>
 <param-value>dev</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
 <servlet-name>appServlet</servlet-name>
 <url-pattern>/</url-pattern>
 </servlet-mapping>
</web-app>


Tags:   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
前言什么是数据脱敏数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护常用脱敏规则替换、重排、加密、截断、掩码良好的数据脱敏实施1、尽...【详细内容】
2021-12-28  Tags: spring  点击:(4)  评论:(0)  加入收藏
河南最有名的“13碗面”,吃过10种以上的一定是地道河南人,你吃过几碗?河南位于黄河中下游,优越的地理位置和条件,让河南的种植业在全国脱颖而出,被称为全国的“粮仓”。小麦是河南...【详细内容】
2021-12-28  Tags: spring  点击:(3)  评论:(0)  加入收藏
在狗界中,有些狗狗比较凶残、霸道,今天我们就来说说被称为“犬中四煞”的4种狗,请认住它们的长相,看见了要绕路走! NO1:黑狼犬产地:中国寿命:11-12年黑狼犬是狼狗的一种,长大高大威猛...【详细内容】
2021-12-28  Tags: spring  点击:(3)  评论:(0)  加入收藏
协议下的体面离婚 2015年1月 方晴供职于一家外企,袁亮硕士毕业后开了家公司。两人相识、恋爱后走进婚姻殿堂。 方晴和袁亮的儿子小浩出生了。本该是其乐融融的三口之家,却在一...【详细内容】
2021-12-28  Tags: spring  点击:(2)  评论:(0)  加入收藏
中国人神话世界五千年到一万年之前到底是一个什么样的世界?相信这个问题应该是困扰了大家许久吧!其实这些问题可以从远古时代的三皇五帝开始说起,三皇五帝对于中国人的影响就如...【详细内容】
2021-12-28  Tags: spring  点击:(2)  评论:(0)  加入收藏
去年有个新闻,说的是一名印度女孩自小被欧洲有钱人家收养,长大后要回来给自己出生的村子捐钱做慈善。等她回村的时候,村里人专门为女孩修了一条路。表面上看,这貌似是个暖心的故...【详细内容】
2021-12-28  Tags: spring  点击:(3)  评论:(0)  加入收藏
日本在今年又给大家带来了一个巨大消息,日本著名的球星本田圭佑出资设立的一家公司,正式发售了飞行摩托车。 在之前可是在电视或者是电影中才能看到的,是具备了未来科幻的一个...【详细内容】
2021-12-28  Tags: spring  点击:(4)  评论:(0)  加入收藏
V社今日公布了2021年Steam最畅销游戏榜单,其中涵盖了本年度Steam上收入最高的100款游戏。为了得出每款游戏的总收入,Steam计算了2021年1月1日至2021年12月15日的游戏销售额、...【详细内容】
2021-12-28  Tags: spring  点击:(3)  评论:(0)  加入收藏
“都怪我一时糊涂铸下大错,这几年为了蒙混过关,拆东墙补西墙就怕被发现,我对不起信任我的领导同事,更对不起我的家人。”内蒙古某国有合资公司原出纳员包某在庭审现场听取公诉人...【详细内容】
2021-12-28  Tags: spring  点击:(2)  评论:(0)  加入收藏
2021年黄金价格下跌11.3%,黄金现在已经下跌了6.5%。白银价格一度下跌19.3%,白银现在已经下跌了15%。美元通胀。白银自2020年2月份以来,五家中央银行(Fed、欧洲中央银行、日本中...【详细内容】
2021-12-28  Tags: spring  点击:(3)  评论:(0)  加入收藏
▌简易百科推荐
近日只是为了想尽办法为 Flask 实现 Swagger UI 文档功能,基本上要让 Flask 配合 Flasgger, 所以写了篇 Flask 应用集成 Swagger UI 。然而不断的 Google 过程中偶然间发现了...【详细内容】
2021-12-23  Python阿杰    Tags:FastAPI   点击:(6)  评论:(0)  加入收藏
文章目录1、Quartz1.1 引入依赖<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency>...【详细内容】
2021-12-22  java老人头    Tags:框架   点击:(12)  评论:(0)  加入收藏
今天来梳理下 Spring 的整体脉络啦,为后面的文章做个铺垫~后面几篇文章应该会讲讲这些内容啦 Spring AOP 插件 (了好久都忘了 ) 分享下 4ye 在项目中利用 AOP + MybatisPlus 对...【详细内容】
2021-12-07  Java4ye    Tags:Spring   点击:(14)  评论:(0)  加入收藏
&emsp;前面通过入门案例介绍,我们发现在SpringSecurity中如果我们没有使用自定义的登录界面,那么SpringSecurity会给我们提供一个系统登录界面。但真实项目中我们一般都会使用...【详细内容】
2021-12-06  波哥带你学Java    Tags:SpringSecurity   点击:(18)  评论:(0)  加入收藏
React 简介 React 基本使用<div id="test"></div><script type="text/javascript" src="../js/react.development.js"></script><script type="text/javascript" src="../js...【详细内容】
2021-11-30  清闲的帆船先生    Tags:框架   点击:(19)  评论:(0)  加入收藏
流水线(Pipeline)是把一个重复的过程分解为若干个子过程,使每个子过程与其他子过程并行进行的技术。本文主要介绍了诞生于云原生时代的流水线框架 Argo。 什么是流水线?在计算机...【详细内容】
2021-11-30  叼着猫的鱼    Tags:框架   点击:(21)  评论:(0)  加入收藏
TKinterThinter 是标准的python包,你可以在linx,macos,windows上使用它,你不需要安装它,因为它是python自带的扩展包。 它采用TCL的控制接口,你可以非常方便地写出图形界面,如...【详细内容】
2021-11-30    梦回故里归来  Tags:框架   点击:(27)  评论:(0)  加入收藏
前言项目中的配置文件会有密码的存在,例如数据库的密码、邮箱的密码、FTP的密码等。配置的密码以明文的方式暴露,并不是一种安全的方式,特别是大型项目的生产环境中,因为配置文...【详细内容】
2021-11-17  充满元气的java爱好者  博客园  Tags:SpringBoot   点击:(25)  评论:(0)  加入收藏
一、搭建环境1、创建数据库表和表结构create table account(id INT identity(1,1) primary key,name varchar(20),[money] DECIMAL2、创建maven的工程SSM,在pom.xml文件引入...【详细内容】
2021-11-11  AT小白在线中  搜狐号  Tags:开发框架   点击:(29)  评论:(0)  加入收藏
SpringBoot开发的物联网通信平台系统项目功能模块 功能 说明 MQTT 1.SSL支持 2.集群化部署时暂不支持retain&will类型消 UDP ...【详细内容】
2021-11-05  小程序建站    Tags:SpringBoot   点击:(56)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条