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个类(真实情况要复杂得多):
- ConfigReader:用于读取及验证配置文件,然后放到内存中;
- ReflectionUtil:用于根据配置文件中的配置进行反射实例化;
- 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 中主要包含以下几步的处理。
- 通过继承自 AbstractBeanDefinitionReader 中的方法,来使用 ResourceLoader 将资源文件路径转换为对应的 Resource 文件;
- 通过 DocumentLoader 对 Resource 文件进行转换,将 Resource 文件转换为 Document 文件;
- 通过实现接口 BeanDefinitionDocumentReader 的 DefaultBeanDefinitionDocumentReader 类对 Document 进行解析,并使用 BeanDefinitonParserDelegate 对 Element 进行解析。
4. 容器的基础(XmlBeanFactory)
接下来开始分析如下代码实现:
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("my_test/bean_factory/beanFactoryTest.xml"));
XmlBeanFactory 初始化时序图如下:
通过时序图可以清楚地看到整个逻辑处理顺序,时序图从 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 等。将方法分类如下:
- 定义了 3 个判断当前资源状态的方法:存在性、可读性、是否处于打开状态。
- 提供了不同资源到 URL、URI、File 类型的转换,以及获取 lastModified 属性、文件名(不带路径信息的文件名,getFilename())方法
- 为了便于操作,提供了基于当前资源创建一个相对资源的方法:createRelative()。
- 在错误处理中需要详细地打印出错的资源文件,因而还提供了 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 源码笔记:容器的基本实现-中【三】。