Spring 系列之 Spring 源码笔记:容器的基本实现-上【二】

bean 是 Spring 中最核心的东西,Spring 就像一个大水桶。

1. 容器的基本用法

bean 的定义:

package my_test.bean_factory;

public class MyTestBean {

	private String testStr = "testStr";

	public String getTestStr() {
		return testStr;
	}

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

配置文件:

<?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="myTestBean" class="my_test.bean_factory.MyTestBean"/>
</beans>

测试:

package my_test.bean_factory;

import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class BeanFactoryTest {

	@Test
	public void testSimpleLoad(){
		BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("my_test/bean_factory/beanFactoryTest.xml"));
		// AbstractBeanFactory 的 getBean 方法
		MyTestBean bean = (MyTestBean) beanFactory.getBean("myTestBean");
		System.out.println(bean.getTestStr());
	}

}

可以看到控制台会输出:
在这里插入图片描述
但是企业中大多数都使用的是 ApplicationContext,它比BeanFactory拥有更强大的功能,后面会有文章详细总结 ApplicationContext,这里只是用于测试。

测试方法中虽然很简单的两行代码,但是内部却执行了很多的复杂的逻辑,下面开始分析。

2. 功能分析

上面的测试代码中完成的功能无非是以下几点:

  • 读取配置文件 beanFactoryTest.xml;
  • 根据 beanFactoryTest.xml 找到对应的类的配置,并实例化;
  • 调用实例的get方法

如果要完成上述的功能,至少需要3个类(真实情况要复杂得多):

  1. ConfigReader:用于读取及验证配置文件,然后放到内存中;
  2. ReflectionUtil:用于根据配置文件中的配置进行反射实例化;
  3. App:用于完成整个逻辑的串联

3. 核心类介绍

3.1.1 DefaultListableBeanFactory

XmlBeanFactory 继承 DefaultListableBeanFactory,而 DefaultListableBeanFactory 是整个 bean 加载的核心部分,是 Spring 注册及加载 bean 的默认实现,DefaultListableBeanFactory 继承了 AbstractAutowireCapableBeanFactory 并实现了 ConfigurableListableBeanFactory 以及 BeanDefinitionRegistry 接口。

层次结构图:
在这里插入图片描述
类图:
在这里插入图片描述
相关类的作用:

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

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

3.1.2 XmlBeanDefinitionReader

XML 配置文件的读取时 Spring 中重要的功能,早期大量采用注解之前确实是这样,虽然现在都是采用注解,但是 XML 配置文件的读取依然还是要掌握的。Spring 中的大部分功能都是以配置作为切入点的,那么可以从 XmlBeanDefinitionReader 中梳理一下资源文件读取、解析及注册的大致脉络,首先看下各个类的功能:

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

经过上面的分析,可以梳理出整个 XML 配置文件读取的大致流程,在XmlBeanDefinitionReader 中主要包含以下几步的处理。
在这里插入图片描述

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

4. 容器的基础(XmlBeanFactory)

接下来开始分析如下代码实现:

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("my_test/bean_factory/beanFactoryTest.xml"));

XmlBeanFactory 初始化时序图如下:

BeanFactoryTest ClassPathResource XmlBeanFactory XmlBeanDefinitionReader new ClassPathResource("beanFactoryTest.xml") 1 resource:Resource 2 new XmlBeanFactory(resource) 3 loadBeanDefinitions(resource) 4 loadedBeanDefinitionNum:int 5 BeanFactory 6 BeanFactoryTest ClassPathResource XmlBeanFactory XmlBeanDefinitionReader

通过时序图可以清楚地看到整个逻辑处理顺序,时序图从 BeanFactoryTest 测试类开始,首先调用 ClassPathResource 的构造函数来构造 Resource 资源文件的实例对象,然后利用 Resource 进行 XmlBeanFactory 的初始化。

4.1 配置文件封装

Spring 的配置文件读取是通过 ClassPathResource 进行封装的

在 Java 中,将不同来源的资源抽象成 URL,通过注册不同的 handler(URLStreamHandler) 来处理不同来源的资源的读取逻辑,一般 handler 的类型使用不同前缀(协议,Protocol)来识别,如“file:”、“http:”、“jar:” 等,然而 URL 没有默认定义相对 ClassPath 或 ServletContext 等资源的 handler,虽然可以注册自己的 URLStreamHandler 来解析特定的 URL 前缀(协议),比如“classpath:”,然而这需要了解 URL 的实现机制,而且 URL 也没有提供基本的方法,如检查当前资源是否存在、是否可读等方法。因而 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;

	String getFilename();

	String getDescription();
}

InputStreamSource 封装任何能返回 InputStream 的类,比如 File、ClassPath 下的资源和 ByteArray 等。它只有一个方法定义:InputStream getInputStream()。

Resource 接口抽象了所有 Spring 内部使用到的底层资源:File、URL、Classpath 等。将方法分类如下:

  1. 定义了 3 个判断当前资源状态的方法:存在性、可读性、是否处于打开状态。
  2. 提供了不同资源到 URL、URI、File 类型的转换,以及获取 lastModified 属性、文件名(不带路径信息的文件名,getFilename())方法
  3. 为了便于操作,提供了基于当前资源创建一个相对资源的方法:createRelative()。
  4. 在错误处理中需要详细地打印出错的资源文件,因而还提供了 getDescription() 方法用于在错误处理中打印信息。

对不同来源的资源文件都有相应的 Resource 实现:文件(FileSystemResource)、Classpath(ClassPathResource)、URL资源(UrlResource)、InputStream 资源(InputStreamResource)、Byte数组(ByteArrayResource)等。
资源文件处理相关类图
在日常开发工作中,资源文件的加载也是经常用到的,可以直接使用 Spring 提供的类,比如希望加载文件时可以这样:

Resource resource = new ClassPathResource("beanFactoryTest.xml");
InputStream inputStream = resource.getInputStream();

得到 inputStream 后,我们就可以按照以前的开发方式进行实现了,并且可以利用 Resource 及其子类为我们提供的诸多特性。

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

// ClassPathResource
@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
@Override
public InputStream getInputStream() throws IOException {
	try {
		return Files.newInputStream(this.file.toPath());
	}
	catch (NoSuchFileException ex) {
		throw new FileNotFoundException(ex.getMessage());
	}
}

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

XmlBeanFactory 的初始化方法有若干种,在这里分析的是使用 Resource 实例作为构造函数参数的办法,代码如下:
在这里插入图片描述
构造函数内部再次调用构造函数:
在这里插入图片描述
时序图中的 XmlBeanDefinitionReader 加载数据就是在这里完成的,但是在加载数据前还有一个调用父类构造函数初始化的过程,跟踪代码到父类 AbstractAutowireCapableBeanFactory 的构造函数中:
在这里插入图片描述
需要说明的是 ignoreDependencyInterface 的主要功能是忽略给定接口的自动装配功能

举例来说,当 A 中有属性 B,那么当 Spring 在获取 A 的 Bean 的时候如果其属性 B 还没有初始化,那么 Spring 会自动初始化 B,但是某些情况下, B 不会被初始化,其中一种情况就是 B 实现了 BeanNameAware 接口。

Spring 中是这样介绍的:自动装配时忽略给定的依赖接口,典型的应用是通过其它方式解析 Application 上下文注册依赖,类似于 BeanFactory 通过 BeanFactoryAware 进行注入或者 ApplicationContext 通过
ApplicationContextAware 进行注入。

由于篇幅原因,下篇文章继续,链接:Spring 系列之 Spring 源码笔记:容器的基本实现-中【三】

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值