1 Spring 的整体架构
Spring 框架是一个分层架构,主要模块如下
1.1 Core Container
核心容器层包含有 spring-core,spring-beans,spring-context,Spring-context-support 和 spring-expression 模块。Core 和 Beans 模块是框架的基础部分,提供IoC9(反转控制)和 DI(依赖注入)特性。
(1)spring-core 模决主要包含 Spring 框架基本的核心工具类,Spring 的其他组件都要用到这个包里的类,Core 模块是其他组件的基本核心。
(2)spring-beans 模块提供了BeanFactory,是工厂模式的一个经典实现,是所有应用都要用到的,它包含访问配置文件、创建和管理 bean 以及进行 IoC 和 DI 操作相关的所有类。
(3)spring-context 模块构建于 Core Beans 模块基础之上,继承了 Beans 的特性,为 Spring 核心提供了大量扩展,添加了对国际化(例如资源绑定)、事件传播、资源加载和对 Context 透明创建的支持。Context 模块同时也支持 J2EE 的一些特性,例如 EJB,JMX 和 基础远程处理。ApplicationContext 接口是 Context 模块的关键。
(4)Spring-context-support 模块支持整合第三方库到Spring应用程序上下文,特别是用于高速缓存(EhCache、JCache)和任务调度(CommonJ、Quartz)的支持。
(5)spring-context-indexer 模块作用是在编译时扫描 @Indexed 注解,确定 bean,生成索引文件。该模块是 Spring 5.x 新增的。
(6)spring-expression 模块提供了强大的表达式语言,用于在运行时查询和操纵对象。它是 JSP 2.1 规范中定义的 unifed expression languag 的扩展。该语言支持设置/获取属性的值,属性的分配,方法的调用,访问数组上下文、容器和索引器、逻辑和算术运算符、命名变量以及从 Spring 的 IoC 容器中根据名称检索对象。它也支持 list 投影、选择和一般的 list 聚合。
1.2 Aop 和 Instrument
(1)spring-aop 模块提供了一个符合AOP要求的面向切面的编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以便干净地解耦。
(2)spring-aspects 模块提供了与AspectJ的集成功能,AspectJ是一个功能强大且成熟的AOP框架。
(3)spring-instrument 提供了类植入(Instrumentation)支持和类加载器的实现,可以在特定的应用服务器中使用。
1.3 Messaging
Spring4.0以后新增了消息(spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。
1.4 Data Access/Integration
数据访问/集成层包含 JDBC,ORM,OXM,JMS 和 Transaction 模块。
(1)spring-jdbc 模块提供一个 JDBC 抽象层,消除冗长的 JDBC 编码和数据库厂商特有的错误代码解析。该模块包含 Spring 对 JDBC 数据访问进行封装的所有类。
(2)spring-orm 模块为对象-关系映射 API,为 JPA,JDO,Hibernate,iBatis 等提供了一个交互层。利用 ORM 封装包,可以混合使用所有 Spring 提供的特性进行 O/R 映射。Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JPA,Hibernate,iBatis 所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
(3)spring-oxm 模块提供了一个对 Object/XML 映射实现的抽象层,Object/XML 映射实现包括 JAXB,Castor,XMLBeans,JiBX 和 XStrearn。
(4)spring-jms 模块主要包含了一些制造和消费消息的特性。Spring4.1以后,提供了与 spring-messaging 模块的集成。
(5)spring-tx 事务模块支持编程和声明式事务管理,这些事务类必须实现特定的接口,并对所有的 POJO 都适用。
1.4 Web
Web 层由 spring-web、spring-webmvc、spring-websocket 和 Portlet 模块组成。
(1)spring-web 模块提供了基础的面向 Web 的集成特性。例如,多文件上传、使用 servlet listeners 初始化 IoC 容器以及一个面向 Web 的应用上下文。它还包含 Spring 远程支持中 Web 的相关部分。
(2)spring-webmv 模块也称为 Web-Servlet 模块,包含用于 web 应用程序的 Spring MVC和 REST Web Services 实现。Spring MVC 框架提供了领域模型代码和 Web 表单之间的清晰分离,并与 Spring Framework 的所有其他功能集成。
(3)spring-websocket 模块是 Spring4.0 以后新增的模块,它提供了 WebSocket 和 SocketJS 的实现。
(4)spring-webmvc-portlet 即 Web-Portlet 模块类似于 Servlet 模块的功能,提供了 Portlet 环境下的 MVC 实现。
(5)spring-webflux 模块是 spring 5.0 中引入的新的反应式Web框架。与Spring MVC不同,它不需要 Servlet API,完全异步和非阻塞, 并通过 Reactor 项目实现 Reactive Streams 规范。 并且可以在诸如 Netty,Undertow 和 Servlet 3.1+ 容器的服务器上运行。
2 容器的基本实现
2.1 容器的基本语法
bean 是 Spring 中最核心的东西,Spring是一个水桶的话,bean就是水桶的水。
首选看一下 bean 的定义
public class TestBean {
private String testStr = "testStr";
public String getTestStr() {
return testStr;
}
public void setTestStr(String testStr) {
this.testStr = testStr;
}
}
Spring 目的是让我们的 bean 能成为纯粹的 POJO,接下来看看配置文件:
<!--配置文件-->
<?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="testBean" class="org.springframework.test.base.TestBean"/>
</beans>
最后编写测试代码
public class Main {
public static void main(String[] args) {
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
TestBean testBean = beanFactory.getBean(TestBean.class);
System.out.println(testBean.getTestStr());
}
}
2.2 功能分析
上述代码完成的功能:
- 读取配置文件 beans2.xml。
- 根据 beans2.xml 中的配置找到对应的类的配置,并实例化。
- 调用实例化后的实例。
2.3 Spring 的结构组成
首先梳理 Spring 的框架结构,从全局的角度了解 Spring 结构组成。
2.3.1 beans 包的层次结构
beans 包中的各个源码包的功能如下。
- src/main/java 用于展现 Spring 的主要逻辑
- src/main/resources 用于存放系统的配置文件
- src/test/iava 用于对主要逻辑进行单元测试
- src/test/resources 用于存放测试用的配直文件
2.3.2 核心类介绍
在正式开始源码分析之前,必要了解 Spring 中核心的两个类。
1)DefaultListableBeanFactory XmlBeanFactory 继承向 DefaultListableBeanFactory,而 DefaultListableBeanFactmy 是整个 bean 加载的核心部分,是 Spring 注册及加载 bean 的默认实现,而对于 XmlBeanFactory 与 DefaultListableBeanFactory 不同的地方其实是在 XmlBeanFactory 中使用了自定义的 XML 读取器 XmlBeanDefinitionReader,实现了个性化的 BeanDefinitionReader 读取, DefaultListableBeanFactory 继承了 AbstractAutowireCapableBeanFactory 并实现了 ConfigurableListableBeanFactory 及 BeanDefinitionRegistry 接口。 相关类图如下。 (1)AliasRegistry:定义对 alias 的简单增删改查。
(2)SimpleAliasRegistry:主要使用 map 作为 alias 的缓存,对接口 AliasRegistry 的实现。 (3)SingletonBeanRegistry:定义对单例的注册及获取。 (4)BeanFactory:定义获取bean及bean的各种属性 (5)DefaultSingletonBeanRegistry:继承SimpleAliasRegistry对接口SingletonBeanRegistry的实现 (6)HierachicalBeanFactory:继承 BeanFactory,在其基础上,增加了对 parentFactory 的支持。 (7)BeanDefinitionRegistry:定义对 BeanDefinition 的各种增删改操作。 (8)FactoryBeanRegistrySupport:在 DefaultSingletonBeanRegistry 的基础上增加对 FactoryBean 的特殊处理。 (9)ConfigurableBeanFactory:提供配置Factory的各种方法 (10)ListableBeanFactory:根据各种条件定义获取bean的配置清单 (11)AbstractBeanFactory:综合 FactoryBeanRegistrySupport 和 ConfigurableBeanFactory 的功能
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
}
(12)AutowireCapableBeanFactory:提供创建bean,自动注入,初始化以及应用bean的后处理器
public interface AutowireCapableBeanFactory extends BeanFactory {
}
(13)AbstractAutowireCapableBeanFactory:综合 AbstractBeanFactory 并对接口 AutowireCapableBeanFactory 的实现
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
}
(14)ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口
public interface ConfigurableListableBeanFactory
extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory {
}
(15)DefaultListableBeanFactory:综合功能,主要是对bean注册后的处理
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
}
(16)XmlBeanFactory 对 DefaultListableBeanFactory 进行了扩展,对于注册及获取bean都是父类实现,主要扩展了从XML文档中读取BeanDefinition,通过XmlBeanDefinitionReader类。
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
2)XmlBeanDefinitionReader XML 置文件的读取是 Spring 重要的功能,Spring 的大部分功能都是以配置作为切入点。因此我们可以从 XmlBeanDefinitionReader 中梳理下资源文件读取、解析及注册的大致脉络。下面是 XmlBeanDefinitionReader 的类图。 各个类的主要作用: (1)ResourceLoader:定义资源加载器,主要用于根据给定的资源文件地址返回对应的 Resource。 (2)BeanDefinitionReader:主要定义资源文件读取并转化 BeanDefinition 的各个功能。 (3)EnvironmentCapable:定义获取 Environment 方法。 (4)DocumentLoader:定义资源文件加载到转换为 Document 的功能。 (5)AbstractBeanDefinitionReader:实现 EnvironmentCapable,BeanDefinitionReader 的功能。 (6)BeanDefinitionDocumentReader:定义读取 Document 并注册 BeanDefinition 功能。 (7)BeanDefinitionParserDelegate:定义解析 Element 的方法。
读取 XML 配置文件的主要流程: (1)通过继承 AbstractBeanDefinitionReader 的方法,来使用 ResourceLoader 将资源文件路径转换为 Resource 文件。 (2)通过 DocumentLoader 对 Resource 文件进行转换,将 Resource 文件转换为 Document 文件。 (3)通过实现接口 BeanDefinitionDocumentReader 的 DefaultBeanDefinitionDocumentReader 类对 Document 进行解析,并使用 BeanDefinitionParserDelegate 对 Element 进行解析。
2.4 容器的基础 XmlBeanFactory
下面深入分析一以下功能的代码实现:
BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
通过下面 XmlBeanFactory 初始化时序图看上面代码的执行逻辑。 首先调用 ClassPathResource 的构造函数来构造 Resource 资源文件的实例对象,,这样后续的资源处理就可以用 Resource 提供的各种服务来操作了,当我们有了 Resource 后就可以进行 XmlBeanFactory 的初始化了。所以首先来看 Resource 资源是如何封装的。
2.4.1 配置文件的封装
Spring 对其内部使用到的资源进行了封装: Resource 接口封装底层资源。
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return exists();
}
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;
@Nullable
String getFilename();
String getDescription();
}
InputStreamSource 任何能返回 InputStream 的类,比如 File,Classpath 下的资源和 Byte Array等。它只有一个方法定义 getlnputStream(),该方法返回一个新的 InputStream 对象。
Resource 接口抽象了所有 Spring 内部使用到的底层资源: File,URL,Classpath 等。对不同来源的资源文件都有相应的 Resource 实现:
- 文件资源:FileSystemResourceClasspath。
- Classpath 资源:ClassPathResource。
- URL 资源:UrlResource。
- InputStream 资源:InputStreamResource。
- Byte 数组:ByteArrayResource。
相关类图如下,包含部分。 有了 Resource 接口便可以对所有资源文件进行统一处理。其实现是非常简单的,以 getlnputStream() 为例,ClassPathResource 中的实现方式便是通过 class 或者 classLoader 提供的底层方法实现。对于 FileSystemResource 更简单,直接使用 FileInputStream 对文件进行是实例化。
public class ClassPathResource extends AbstractFileResolvingResource {
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
}
public class FileSystemResource extends AbstractResource implements WritableResource {
@Override
public InputStream getInputStream() throws IOException {
try {
return Files.newInputStream(this.filePath);
}
catch (NoSuchFileException ex) {
throw new FileNotFoundException(ex.getMessage());
}
}
}
当通过 Resource 相关类完成了对配置文件进行封装后配置文件的读取工作就交给 XmlBeanDefinitionReader 来处理了。
下面分析使用 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)是资源加载的真正实现,但是在加载数据之前还有一个调用父类构造方法的过程 super(parentBeanFactory)。跟踪代码到父类 AbstractAutowireCapableBeanFactory 的构造函数中。
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
this();
setParentBeanFactory(parentBeanFactory);
}
ignoreDependencyInterface 的主要功能是忽略给定接口的自动装配功能。 实现了 BeanNameAware 接口的类,不会自动初始化:典型应用是通过其它方式解析 Application 上下文注册,类似于 BeanFactory 通过 BeanFactoryAware 进行注入,ApplicationContext 通过 ApplicationContextAware 进行注入。
2.4.2 加载 Bean
XmlBeanFactory 构造函数中调用了 XmlBeanDefinitionReader 类型的 reader 属性提供的方法 loadBeanDefinition(Resource resource) 方法进行加载。
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
...
}
this.reader.loadBeanDefinition(Resource resource) 这句代码是整个资源加载的切入点,下面是这个方法的时序图。 1)封装资源文件,当进入 XmlBeanDefinitionReader 后首先对参数 resource 使用 EncodedResource 进行封装。 2)获取输入流。从 Resource 中获取对应的 Inputstream 并构造 InputSource。 3)通过构造的 InputSource 和 Reource 继续调用 doLoadBeanDefinitions。
首先看下 loadBeanDefinitions 函数的具体实现:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
其中 EncodedResource 的作用住哟啊用于对资源文件的编码进行处理。其中主要的逻辑在方法 getReader() 中,当设置了编码属性时 Spring 会使用相应的编码作为输入流的编码。
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
else {
return new InputStreamReader(this.resource.getInputStream());
}
}
上述代码构造了一个 InputStreamReader。构造完 encodedResource 对象后,会调用loadBeanDefinitions(new EncodedResource(resource)) 方法。该方法内部是真正的数据准备阶段。
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
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, encodedResource.getResource()) 。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
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);
}
}
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
上述代码不考虑异常处理的代码,主要做了以下事情。 1)获取 EntityResolver 对象。通过getEntityResolver() 方法获取。 2)获取对 XML 文件的验证模式。通过getValidationModeForResource(resource) 方法实现。 3)加载 XML 文件,并获取对应的 Document。通过this.documentLoader.loadDocument 方法实现。 4)根据返回的 Document 注册 Bean 信息。通过registerBeanDefinitions(doc, resource) 方法实现。
2.5 获取 EntityResolver
2.5.1 entityResolver 的获取
doLoadBeanDefinitions 方法中调用 doLoadDocument 方法,doLoadDocument 调用 getEntityResolver 方法获取 entityResolver。
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
}
else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
2.5.2 EntityResolver 用法
如果 SAX 应用程序需要实现自定义处理外部实体,则必须实现此接口并使用 setEntityResolver 方法向 SAX 驱动器注册一个实例。即对于解析 XML, SAX 首先读取该 XML 文档上的声明,根据声明去寻找相应的 DTD 定义,以便对文档进行验证。默认通过网络(实现上就是声明的 DTD 的 URI 地址)来下载相应的 DTD 声明,并进行认证。当网络中断或不可用时,会导致相应的 DTD 声明找不到。
EntityResolver 的作用是项目本身提供一个如何寻找 DTD 声明的方法,由程序来实现寻找 DTD 声明的过程,将 DTD 文件放到项目某处,在实现时直接将此文档读取返回给SAX即可。可以避免提供网络来寻找相应的声明。
主要使用到resolveEntity (String publicId, String systemId) 方法。该方法接收 publicId 和 systemId 两个参数,并返回 InputSource 对象。
InputSource resolveEntity (String publicId, String systemId);
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.dtd">
<beans>
...
</beans>
获取参数为:
- publicId = -//Spring/DTD BEAN 2.0//EN
- systemId = http://www.springframework.org/dtd/spring-beans.dtd
验证文件默认加载方式是通过 URL 进行下载,这样会造成延迟。因此更好的做法是将验证文件放置到工程目录下,并通过一定的逻辑将 URL 转换为工程对应的地址。根据之前getEntityResolver() 方法可知,Spring 中 EntityResolver 默认的实现是 DelegatingEntityResolver 类。其 resolveEntity 实现方法如下:
public InputSource resolveEntity(@Nullable 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;
}
如果是 DTD,调用 BeansDtdResolver 来解析。直接截取 systemld 最后的 xx.dtd 然后去当前路径下寻找。
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("...");
}
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.isTraceEnabled()) {
logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
}
return source;
}
catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
}
}
}
}
return null;
}
如果是 XSD,调用 PluggableSchemaResolver 解析。默认到 META Spring.schemas 文件巾找到 system id 所对应的 XSD 文件并加载
public InputSource resolveEntity(@Nullable 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) {
String resourceLocation = getSchemaMappings().get(systemId);
if (resourceLocation == null && systemId.startsWith("https:")) {
resourceLocation = getSchemaMappings().get("http:" + systemId.substring(6));
}
if (resourceLocation != null) {
Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
try {
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isTraceEnabled()) {
logger.trace("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
}
return source;
}
catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find XML schema [" + systemId + "]: " + resource, ex);
}
}
}
}
return null;
}
2.6 获取 XML 的验证模式
XML 文件的验证模式保证了 XML 文件的准确性,其中常用的验证模式有两种:DTD 和 XSD。
2.6.1 DTD 和 XSD 区别
1)DTD(Document Type Definition) 文档类型定义,是一种 XML 约束模式语言,是 XML 的验证机制,属于 XML 文件的一部分。保证 XML 文档格式正确的有效方法。一个 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.dtd">
<beans>
...
</beans>
<!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-init-method CDATA #IMPLIED>
<!ATTLIST beans default-destroy-method CDATA #IMPLIED>
...
2)XML Schema 语言是就是 XSD(XML Schema Definition)。 (1)XML Schema 描述了XML文档的结构。可以用一个指定的 XML Schema 结构来验证某个 XML 文档。其本身就是一个 XML 文档,符合 XML 语法,可以使用通用的 XML 解析器解析。 (2)在使用 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-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tool/spring-tx.xsd">
...
</beans>
spring-beans.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>
...
</xsd:schema>
2.6.2 验证模式的读取
验证模式种类
public static final int VALIDATION_NONE = XmlValidationModeDetector.VALIDATION_NONE;
public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;
public static final int VALIDATION_DTD = XmlValidationModeDetector.VALIDATION_DTD;
public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD;
源码 doLoadBeanDefinitions 方法中调用 doLoadDocument 方法,doLoadDocument 调用 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;
}
return VALIDATION_XSD;
}
上述代码中,如果设定了验证模式则使用设定的验证模式(可以使用 XmlBeanDefinitionReader 的 setValidationMode 方法进行设定),否则使用自动检测。自动检测实在函数detectValidationMode 方法中实现的,在该方法中将自动检测验证模式委托给 XmlValidationModeDetector 的detectValidationMode 方法。
protected int detectValidationMode(Resource resource) {
if (resource.isOpen()) {
throw new BeanDefinitionStoreException(...);
}
InputStream inputStream;
try {
inputStream = resource.getInputStream();
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(...);
}
try {
return this.validationModeDetector.detectValidationMode(inputStream);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(...);
}
}
调用 XmlValidationModeDetector#detectValidationMode 方法。
public int detectValidationMode(InputStream inputStream) throws IOException {
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)) {
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
Spring 来检测验证模式的办法就是判断是否包含 DOCTYPE ,如果包含就是 DTD ,否则就是 XSD。
2.7 获取 Document
获取 Document 委托给了 DefaultDocumentLoader 类的 loadDocument 方法去实现。
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
factory.setValidating(true);
if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
factory.setNamespaceAware(true);
try {
factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
}
catch (IllegalArgumentException ex) {
ParserConfigurationException pcex = new ParserConfigurationException(...);
pcex.initCause(ex);
throw pcex;
}
}
}
return factory;
}
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
throws ParserConfigurationException {
DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
return docBuilder;
}
上述代码通过 SAX 解析 XML文档。
2.8 解析及注册 BeanDefinition
当通过 DefaultDocumentLoader 的 doLoadDocument(inputSource, resource) 加载获取Document 后,会调用 registerBeanDefinitions(doc, resource) 方法注册bean。其中参数 doc 通过 loadDocument 载转换出来的。
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;
}
1)实例化 DefaultBeanDefinitionDocumentReader,XmlBeanDefinitionReader 将注册解析BeanDefinition的逻辑委托给了该类,这样很好的体现了单一职责。
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanUtils.instantiateClass(this.documentReaderClass);
}
2)获取已经注册 BeanDefinition 的个数。
int countBefore = getRegistry().getBeanDefinitionCount();
3)调用 DefaultBeanDefinitionDocumentReader 的 registerBeanDefinitions(doc, readerContext) 方法进行注册。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
上述方法主要是获取 doc 的 root,然后再次将 root 作为参数调用重载方法 doRegisterBeanDefinitions(root)。
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(readerContext, 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(!readerContext.getEnvironment().acceptsProfiles(Profiles.of(specifiedProfiles))) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
上述代码逻辑: (1)对 profile 属性的处理。 (2)解析前处理(preProcessXml 方法),留给子类实现。 (3)调用 parseBeanDefinitions 方法进行 document 的解析。
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);
}
}
上述方法首先判断是默认标签和自定义标签,判断方法是根节点是否是默认命名空间(使用 node.getNamespaceURI() 获取名称空间,并与 Sping 中规定的命名空间http://www.springframework.org/schema/beans 比较)。如果是默认的调用parseDefaultElement(ele, delegate) 方法,如果是自定义的调用delegate.parseCustomElement(ele) 方法。 (4)解析后处理(postProcessXml 方法),留给子类实现。
4)返回本次加载 BeanDefinition 的个数。
2.9 附录
2.9.1 profile 属性的使用
该属性可以用来在配置文件中部署两套配置试用于不同的环境。
1)配置文件
<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 profile="pro">
<bean id="proTestBean" class="org.springframework.test.profiles.ProTestBean"></bean>
</beans>
<beans>
<bean id="defaultTestBean" class="org.springframework.test.profiles.DefaultTestBean"></bean>
</beans>
<beans profile="dev">
<bean id="devTestBean" class="org.springframework.test.profiles.DevTestBean"></bean>
</beans>
</beans>
集成到 web 环境:
// web.xml
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>dev</param-value>
</context-param>
使用 JVM 参数配置
-Dspring.profiles.active=dev
2)解析时机。在 root 节点进行 profile 属性的处理时。
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(!readerContext.getEnvironment().acceptsProfiles(Profiles.of(specifiedProfiles))) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
}
return;
}
}
}
readerContext.getEnvironment().acceptsProfiles(Profiles.of(specifiedProfiles)) ,会将配置信息初始化到 StandardEnvironment 环境中去。
public boolean acceptsProfiles(Profiles profiles) {
Assert.notNull(profiles, "Profiles must not be null");
return profiles.matches(this::isProfileActive);
}
protected boolean isProfileActive(String profile) {
validateProfile(profile);
Set<String> currentActiveProfiles = doGetActiveProfiles();
return (currentActiveProfiles.contains(profile) ||
(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
}
2.9.2 XmlReaderContext
上一小结代码中使用到了 XmlReaderContext 的实例 readerContext。该类的作用是 在bean定义读取过程中传递的上下文,封装了所有相关的配置和状态。继承自 ReaderContext。相关源码如下:
public class XmlReaderContext extends ReaderContext {
private final XmlBeanDefinitionReader reader;
private final NamespaceHandlerResolver namespaceHandlerResolver;
public XmlReaderContext(
Resource resource, ProblemReporter problemReporter,
ReaderEventListener eventListener, SourceExtractor sourceExtractor,
XmlBeanDefinitionReader reader, NamespaceHandlerResolver namespaceHandlerResolver) {
super(resource, problemReporter, eventListener, sourceExtractor);
this.reader = reader;
this.namespaceHandlerResolver = namespaceHandlerResolver;
}
public final XmlBeanDefinitionReader getReader() {
return this.reader;
}
public final BeanDefinitionRegistry getRegistry() {
return this.reader.getRegistry();
}
...
}
|