【Spring 源码深度解析】容器基本实现

【Spring 源码深度解析】容器基本实现,第1张

【Spring 源码深度解析】容器基本实现

文章目录
  • 1 Spring 的整体架构
    • 1.1 Core Container
    • 1.2 Aop 和 Instrument
    • 1.3 Messaging
    • 1.4 Data Access/Integration
    • 1.4 Web
  • 2 容器的基本实现
    • 2.1 容器的基本语法
    • 2.2 功能分析
    • 2.3 Spring 的结构组成
      • 2.3.1 beans 包的层次结构
      • 2.3.2 核心类介绍
    • 2.4 容器的基础 XmlBeanFactory
    • 2.4.1 配置文件的封装
      • 2.4.2 加载 Bean
    • 2.5 获取 EntityResolver
      • 2.5.1 entityResolver 的获取
      • 2.5.2 EntityResolver 用法
    • 2.6 获取 XML 的验证模式
      • 2.6.1 DTD 和 XSD 区别
      • 2.6.2 验证模式的读取
    • 2.7 获取 document
    • 2.8 解析及注册 BeanDefinition
    • 2.9 附录
      • 2.9.1 profile 属性的使用
      • 2.9.2 XmlReaderContext

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 的定义

//bean 的定义
public class TestBean {
	private String testStr = "testStr";

	public String getTestStr() {
		return testStr;
	}

	public void setTestStr(String testStr) {
		this.testStr = testStr;
	}
}

Spring 目的是让我们的 bean 能成为纯粹的 POJO,接下来看看配置文件:





    
    

最后编写测试代码

//测试类
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);
		//加载 beanDefinitions
		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 实现:

  1. 文件资源:FileSystemResourceClasspath。
  2. Classpath 资源:ClassPathResource。
  3. URL 资源:UrlResource。
  4. InputStream 资源:InputStreamResource。
  5. Byte 数组:ByteArrayResource。

相关类图如下,包含部分。

有了 Resource 接口便可以对所有资源文件进行统一处理。其实现是非常简单的,以 getlnputStream() 为例,ClassPathResource 中的实现方式便是通过 class 或者 classLoader 提供的底层方法实现。对于 FileSystemResource 更简单,直接使用 FileInputStream 对文件进行是实例化。

// ClassPathResource.java
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;
	}
}

// FileSystemResource.java
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 实例作为构造参数的初始化方法。

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

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
	// 调用父类构造方法
	super(parentBeanFactory);
	//加载 beanDefinitions
	this.reader.loadBeanDefinitions(resource);
}

上面函数中的代码 this.reader.loadBeanDefinitions(resource)是资源加载的真正实现,但是在加载数据之前还有一个调用父类构造方法的过程 super(parentBeanFactory)。跟踪代码到父类 AbstractAutowireCapableBeanFactory 的构造函数中。

// AbstractAutowireCapableBeanFactory.java
public AbstractAutowireCapableBeanFactory() {
	super();
	// 忽略给定接口的自动装配功能
	ignoreDependencyInterface(BeanNameAware.class);
	ignoreDependencyInterface(BeanFactoryAware.class);
	ignoreDependencyInterface(BeanClassLoaderAware.class);
}


public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
	// 调用上述构造方法
	this();
	// 设置父 factory
	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 {
	//封装Resource,编码需求
	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 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 {
		//从 encodedResource 中获取已经封装的 Resource 对象并再次从 Resource 获取其中 inputStream
		InputStream inputStream = encodedResource.getResource().getInputStream();
		try {
			//封装成 InputSource 对象,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, encodedResource.getResource())。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
		throws BeanDefinitionStoreException {

	try {
		// 使用 DefaultdocumentLoader 将 Resource 转换为 document 对象
		document doc = doLoaddocument(inputSource, resource);
		//根据 document 实例注册 bean 信息
		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 {
	//使用 DefaultdocumentLoader 解析转换文档
	// getValidationModeForResource 获取XML的验证模式(DTD 和 XSD)
	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) {
		// Determine default EntityResolver to use.
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader != null) {
			// 在 AbstractBeanDefinitionReader 构造方法中已经实例化了 resourceLoader
			// 因此会实例化 ResourceEntityResolver extends DelegatingEntityResolver
			// this.resourceLoader = new PathMatchingResourcePatternResolver();
			// 	-> this.resourceLoader = new DefaultResourceLoader();
			// 	 -> this.classLoader = ClassUtils.getDefaultClassLoader();
			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。




    ...

获取参数为:

  1. publicId = null
  2. systemId = http://www.springframework.org/schema/beans/spring-beans.xsd

2)如果解析的验证模式为 DTD。





 ...

获取参数为:

  1. publicId = -//Spring/DTD BEAN 2.0//EN
  2. systemId = http://www.springframework.org/dtd/spring-beans.dtd

验证文件默认加载方式是通过 URL 进行下载,这样会造成延迟。因此更好的做法是将验证文件放置到工程目录下,并通过一定的逻辑将 URL 转换为工程对应的地址。根据之前getEntityResolver()方法可知,Spring 中 EntityResolver 默认的实现是 DelegatingEntityResolver 类。其 resolveEntity 实现方法如下:

// DelegatingEntityResolver.java
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
	throws SAXException, IOException {

	if (systemId != null) {
		//如果是dtd从这里解析,去当前路径下寻找
		if (systemId.endsWith(DTD_SUFFIX)) {
			return this.dtdResolver.resolveEntity(publicId, systemId);
		}
		else if (systemId.endsWith(XSD_SUFFIX)) {
			//调用meta-INF/Spring.schemas 解析
			return this.schemaResolver.resolveEntity(publicId, systemId);
		}
	}

	// Fall back to the parser's default behavior.
	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('/');
		//  截取 systemld 最后的 spring-beans.dtd
		int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
		if (dtdNameStart != -1) {
			//文件名称:spring-beans.dtd
			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);
				}
			}
		}
	}

	// Fall back to the parser's default behavior.
	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) {
		// 加载 meta-INF/spring.schemas 下的 .xml 文件,并从中获取当前 syytemId 对应的文件名称
		String resourceLocation = getSchemaMappings().get(systemId);
		if (resourceLocation == null && systemId.startsWith("https:")) {
			// Retrieve canonical http schema mapping even for https declaration
			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);
				}
			}
		}
	}

	// Fall back to the parser's default behavior.
	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 声明方式的代码。





 ...










...

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")。




    ...

spring-beans.xsd 部分代码。





	

	
			
	
	...
	

2.6.2 验证模式的读取

验证模式种类

// XmlBeanDefinitionReader.java

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;
	}
	// xsd:
	return VALIDATION_XSD;
}

上述代码中,如果设定了验证模式则使用设定的验证模式(可以使用 XmlBeanDefinitionReader 的 setValidationMode方法进行设定),否则使用自动检测。自动检测实在函数detectValidationMode方法中实现的,在该方法中将自动检测验证模式委托给 XmlValidationModeDetector 的detectValidationMode方法。

// XmlBeanDefinitionReader.java
protected int detectValidationMode(Resource resource) {
	if (resource.isOpen()) {
		throw new BeanDefinitionStoreException(...);
	}

	InputStream inputStream;
	try {
		//获取输入流
		inputStream = resource.getInputStream();
	}
	catch (IOException ex) {
		throw new BeanDefinitionStoreException(...);
	}

	try {
		//委托给 XmlValidationModeDetector 类实现
		return this.validationModeDetector.detectValidationMode(inputStream);
	}
	catch (IOException ex) {
		throw new BeanDefinitionStoreException(...);
	}
}

调用 XmlValidationModeDetector#detectValidationMode 方法。

// XmlValidationModeDetector.java
public int detectValidationMode(InputStream inputStream) throws IOException {
	// 如果包含 DOCTYPE 就是DTD,否则是 XSD
	BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
	try {
		// 是DTD文档
		boolean isDtdValidated = false;
		String content;
		// 读取第一行非 null
		while ((content = reader.readLine()) != null) {
			//解析行
			content = consumeCommentTokens(content);
			if (this.inComment || !StringUtils.hasText(content)) {
				continue;
			}
			// 是否包含DOCTYPE
			if (hasDoctype(content)) {
				isDtdValidated = true;
				break;
			}
			if (hasOpeningTag(content)) {
				// End of meaningful data...
				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 方法去实现。

// DefaultdocumentLoader.java
public document loaddocument(InputSource inputSource, EntityResolver entityResolver,
	ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

	//先创建 documentBuilderFactory
	documentBuilderFactory factory = createdocumentBuilderFactory(validationMode, namespaceAware);
	if (logger.isTraceEnabled()) {
		logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
	}
	//通过工厂创建构建者
	documentBuilder builder = createdocumentBuilder(factory, entityResolver, errorHandler);
	//解析成document对象
	return builder.parse(inputSource);
}

protected documentBuilderFactory createdocumentBuilderFactory(int validationMode, boolean namespaceAware)
		throws ParserConfigurationException {

	//documentBuilderFactoryImpl
	documentBuilderFactory factory = documentBuilderFactory.newInstance();
	factory.setNamespaceAware(namespaceAware);

	//如果需要验证
	if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
		//设为true
		factory.setValidating(true);
		//如果为XSD验证模式
		if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
			// Enforce namespace aware for XSD...
			//设置名称空间为true
			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 {

	//documentBuilderImpl
	documentBuilder docBuilder = factory.newdocumentBuilder();
	//设置 entityResolver
	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 载转换出来的。

// XmlBeanDefinitionReader.java
public int registerBeanDefinitions(document doc, Resource resource) throws BeanDefinitionStoreException {
	//使用 createBeanDefinitiondocumentReader 实例化 DefaultBeanDefinitiondocumentReader
	BeanDefinitiondocumentReader documentReader = createBeanDefinitiondocumentReader();
	//在实例化 BeanDefinitionReader 时候将 BeanDefinitionRegistry 传入,默认
	//使用继承自 DefaultListableBeanFactory 的子类
	//记录统计前 BeanDefinition 的加载个数
	int countBefore = getRegistry().getBeanDefinitionCount();
	//加载及注册bean,通过 DefaultBeanDefinitiondocumentReader
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	//记录本次加载的 BeanDefinitions
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

1)实例化 DefaultBeanDefinitiondocumentReader,XmlBeanDefinitionReader 将注册解析BeanDefinition的逻辑委托给了该类,这样很好的体现了单一职责。

protected BeanDefinitiondocumentReader createBeanDefinitiondocumentReader() {
	// documentReaderClass 默认为 DefaultBeanDefinitiondocumentReader
	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;
	//获取 root 节点,再次将 root(beans) 作为参数继续BeanDefinition 的注册
	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)) {
		//获取 profile 属性
		String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
		if(StringUtils.hasText(profileSpec)) {
			// 配置了多个,分隔符进行分割
			String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec,
					BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			//这里进行 profile 的判断,如果配置了例如 -Dspring.profiles.active=dev 之类的JVM参数
			//在这里会将参数写入到 Environment中
			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) {
	//对 beans 的处理,根节点是beans
	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;
				//isDefaultNamespace :node.getNamespaceURI() 返回的uri 和
				//"http://www.springframework.org/schema/beans" 对比
				if (delegate.isDefaultNamespace(ele)) {
					//默认标签的解析 如 
					parseDefaultElement(ele, delegate);
				}
				else {
					//对bean 的处理,自定义的
					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)配置文件



	
		
	
	
		
	
	
		
	

集成到 web 环境:

// web.xml

    spring.profiles.active
    dev

使用 JVM 参数配置

-Dspring.profiles.active=dev

2)解析时机。在 root 节点进行 profile 属性的处理时。

// DefaultBeanDefinitiondocumentReader#doRegisterBeanDefinitions
if(this.delegate.isDefaultNamespace(root)) {
	//获取 profile 属性
	String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
	if(StringUtils.hasText(profileSpec)) {
		// 配置了多个,分隔符进行分割
		String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec,
				BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
		//这里进行 profile 的判断,如果配置了例如 -Dspring.profiles.active=dev 之类的JVM参数
		//在这里会将参数写入到 Environment中
		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 环境中去。

// AbstractEnvironment.java
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);
	//这里进行 activeProfiles 的集合的获取,懒加载
	Set currentActiveProfiles = doGetActiveProfiles();
	//优先级 spring.profiles.active > spring.profiles.default
	//spring.profiles.default 默认值为 (default)
	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();
	}
	...
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5686966.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存