【重写SpringFramework】ApplicationContext基本实现(chapter 3-2)
1. 前言
上一节我们对 ApplicationContext
的继承体系进行了梳理,从应用类型和配置方式两个维度来划分众多的实现类。我们关心的是以声明注解的方式来配置的 Spring 容器,具体到 context 模块,ApplicationContext
的实现主要为普通应用服务。本节我们将专注于 context 模块有关的接口和类,分析各自的功能,并实现一个最简单的 Spring 容器。
2. 整体分析
2.1 继承结构
从类图上来看,ApplicationContext
与 BeanFactory
通过装饰模式组合在一起,上一节已经论述过了。这里简化了 BeanFactory
的继承结构,我们重点关心 ApplicationContext
的相关接口的类,如下所示:
-
ApplicationContext
:顶级接口,继承了BeanFactory
接口 -
ConfigurableApplicationContext
:对外暴露的配置接口,可以进行一些设置工作 -
AbstractApplicationContext
:顶级抽象类,实现了ApplicationContext
接口的大多数功能 -
GenericApplicationContext
:简单的实现类,拥有容器的基本功能,主要用于编写测试代码 -
AnnotationConfigApplicationContext
:通过注解方式进行配置的,是本模块中最重要的实现类 -
AbstractRefreshableApplicationContext
:可刷新的容器,允许多次调用 refresh 方法,本模块不涉及实现类
2.2 核心 API 简介
ApplicationContext
作为顶级接口,最重要的作用是继承了 BeanFactory
接口,在装饰模式中充当抽象装饰角色。
getAutowireCapableBeanFactory
方法:获取一个内部的BeanFactory
对象getEnvironment
方法:获取环境变量对象,Environment
保存了系统和配置文件等介质的相关信息
public interface ApplicationContext extends BeanFactory {
String getId();
String getDisplayName();
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
ConfigurableEnvironment getEnvironment();
}
ConfigurableApplicationContext
是对外暴露的配置接口,与 ConfigurableBeanFactory
接口类似,可以进行一些设置工作。
addBeanFactoryPostProcessor
方法:添加BeanFactoryPostProcessor
组件,我们在第一章 beans 模块简单介绍过该组件,具体用法将在配置类说明refresh
方法:定义了容器初始化的流程getBeanFactory
方法:获取内部的BeanFactory
实例,可以进行配置操作
public interface ConfigurableApplicationContext extends ApplicationContext {
void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);
void refresh();
void setEnvironment(ConfigurableEnvironment environment);
ConfigurableBeanFactory getBeanFactory() throws IllegalStateException;
void setParent(ApplicationContext parent);
}
AbstractApplicationContext
作为顶级的抽象类,提供了 ApplicationContext
接口的大部分功能,最重要的就是 refresh
方法。所有的实现类都会在构造方法中调用 refresh
方法,刷新上下文,使 Spring 容器变得可用。
public abstract class AbstractApplicationContext implements ConfigurableApplicationContext {
@Override
public void refresh() {
//实现略
}
}
GenericApplicationContext
是一个基本实现类,最大的特点是持有一个 DefaultListableBeanFactory
实例,相当于装饰模式中的具体装饰角色。该类有两个作用,一是作为测试代码使用的 Spring 容器,二是作为整个继承体系中的一环,起到过渡作用。本章我们更关心子类 AnnotationConfigApplicationContext
的表现,这里先不展开讲。
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private final DefaultListableBeanFactory beanFactory;
public GenericApplicationContext() {
this.beanFactory = new DefaultListableBeanFactory();
}
}
AbstractRefreshableApplicationContext
是另一个基本实现类,从类图中可以看到它与 GenericApplicationContext
是平行关系。该类的特点是可刷新(Refreshable),刷新的动作是由配置文件触发的。也就是说,该类型的应用会对配置文件进行监控,一旦配置文件发生变更,就会刷新整个容器。我们不关注基于 XML 的实现类,这里仅作为继承结构中的一环,为后续的 Web 应用做铺垫。
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
private DefaultListableBeanFactory beanFactory;
@Override
protected void refreshBeanFactory() throws BeansException {
this.beanFactory = new DefaultListableBeanFactory();
loadBeanDefinitions(beanFactory);
}
//模版方法,由子类实现具体的加载逻辑
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException;
}
3. AbstractApplicationContext
3.1 基本属性
AbstractApplicationContext
是整个 ApplicationContext
体系中的核心类,refresh
方法更是重中之重。我们先介绍一些基本的属性,后续还会陆续增加新的属性,以及接口和父类。基本属性如下所示:
id
:当前容器的唯一标识符displayName
:当前容器的名称,方便用户识别environment
:环境变量相关parent
:可能存在的父容器startupShutdownMonitor
:容器启动或关闭的监视器锁active
:容器的启动标识符closed
:容器的关闭标识符beanFactoryPostProcessors
:容器持有的BeanFactoryPostProcessor
集合
public abstract class AbstractApplicationContext implements ConfigurableApplicationContext {
private String id = ObjectUtils.identityToString(this);
private String displayName = ObjectUtils.identityToString(this);
private ConfigurableEnvironment environment = new StandardEnvironment();
private ApplicationContext parent;
private final Object startupShutdownMonitor = new Object();
private final AtomicBoolean active = new AtomicBoolean();
private final AtomicBoolean closed = new AtomicBoolean()
private final List<BeanFactoryPostProcessor> beanFactoryPostProcessors = new ArrayList<>();
}
3.2 refresh 方法
refresh
方法定义了 Spring 上下文刷新的主体流程,子类可以重写一些模板方法完成一些定制功能,这是模板方法模式的典型应用。由于刷新上下文的工作包含众多流程,Spring 将每个步骤提取为一个方法,主要有 11 步。为了便于理解,我们将这些步骤分为三个部分:
- 准备工作(1~3):提供了一个可用的
BeanFactory
实例。(从第 4 步开始,代码被包裹在 try catch 块中,一旦报错就会销毁单例,并结束创建过程) - 上下文配置(4~8):主要包括两个方面,一是注册容器内置的组件,二是通过多种方式加载
BeanDefinition
,其中一部分会被实例化。 - 收尾工作(9~11):在此之前,所有的
BeanDefinition
都已经被加载了,其中只有一部分实例化了。因此首要工作是要确保所有的单例都被实例化,还有就是事件相关的处理以及其他工作。
@Override
public void refresh() {
//1. 准备工作
//step-1 刷新前的准备
prepareRefresh();
//step-2 通知子类刷新BeanFactory
ConfigurableBeanFactory beanFactory = obtainFreshBeanFactory();
//step-3 ApplicationContext使用BeanFactory前的准备工作,比如添加必要的组件
prepareBeanFactory(beanFactory);
try {
//2. 上下文配置
//step-4 扩展点,允许子类进行自定义操作(主要是与BeanFactory有关的)
postProcessBeanFactory(beanFactory);
//step-5 执行BeanFactoryPostProcessor的相关逻辑
invokeBeanFactoryPostProcessors(beanFactory);
//step-6 注册BeanPostProcessor
registerBeanPostProcessors(beanFactory);
//step-7 注册事件多播器,默认为SimpleApplicationEventMulticaster
initApplicationEventMulticaster(beanFactory);
//step-8 扩展点,允许子类进行自定义操作(与BeanFactory无关)
onRefresh();
//3. 收尾工作
//step-9 将所有的监听器添加到事件多播器中
registerListeners();
//step-10 创建容器中剩余的单例Bean
finishBeanFactoryInitialization(beanFactory);
//step-11 发送refresh完成事件
finishRefresh();
} catch (Exception e) {
//销毁已存在的单例(略)
}
}
4. refresh 方法详解
4.1 准备工作
第 1 步,刷新前的准备工作,主要是给一些标记字段赋值。
//step-1 刷新前的准备,将Context设置为活跃的
private void prepareRefresh() {
this.closed.set(false);
this.active.set(true);
}
第 2 步,获取 BeanFactory
实例。对于 GenericApplicationContext
来说,默认使用 DefaultListableBeanFactory
作为 IOC 容器,并且是一个空的容器。
//step-2 通知子类刷新BeanFactory
private ConfigurableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
refreshBeanFactory
方法是一个模板方法,子类可以重写。比如 ClassPathXmlApplicationContext
通过 XmlBeanDefinitionReader
加载 spring.xml
文件,其父类重写了该方法,实现了加载 Xml 配置文件的功能。示例代码如下:
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
//读取xml配置文件并加载Bean
reader.loadBeanDefinitions(getConfigResources());
}
}
第 3 步,在获得 BeanFactory
实例之后,还需要设置一些组件,使之变得可用。在 Spring 应用中,我们可以直接注入 BeanFactory
、ApplicationContext
、Environment
等组件,原因在于这些组件提前被设置为了默认的依赖项。prepareBeanFactory
方法实现了四种操作,如下所示:
- 设置
BeanFactory
的属性,包括ClassLoader
、BeanExpressionResolver
、PropertyEditorRegistrar
等,我们不是很关心这些组件,仅了解 - 注册
BeanPostProcessor
,其中ApplicationContextAwareProcessor
负责处理Aware
感知接口,该类的逻辑较为简单,自行参看代码。 - 注册用于依赖注入的组件,这些类型都与
BeanFactory
或ApplicationContext
有关,它们都是 Spring 容器的不同表现形式。 - 注册单例,将环境变量对象添加到 Spring 容器中。
//step-3 BeanFactory要变得可用,还需要设置一些组件
private void prepareBeanFactory(ConfigurableBeanFactory beanFactory) {
//1) 设置BeanFactory的相关组件(OMIT)
//2) 注册BeanPostProcessor
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)); //处理Aware感知接口
//注册ApplicationListenerDetector(TODO)
//3)注册依赖项组件
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
//注册ApplicationEventPublisher (TODO)
//4) 注册单例
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}
4.2 上下文配置
第 4 步,postProcessBeanFactory
方法是空实现,允许子类对 BeanFactory
进行自定义的操作。比如子类想添加 BeanPostProcessor
,这个节点是非常好的选择。示例代码如下,支持 web 应用的 ApplicationContext
子类中,添加了用于处理 ServletContext
的 BeanPostProcessor
组件。
public abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableApplicationContext {
@Override
protected void postProcessBeanFactory(ConfigurableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
}
}
第 5 步,查找容器中的 BeanFactoryPostProcessor
,依次执行相关操作。最典型的应用是处理声明了 @Configuration
的配置类,大部分组件都是在这一过程中被加载的。
//step-5 调用已注册的BeanFactoryPostProcessor
protected void invokeBeanFactoryPostProcessors(ConfigurableBeanFactory beanFactory) {
List<BeanDefinitionRegistryPostProcessor> processors = new ArrayList<>();
List<String> names = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class);
for (String name : names) {
processors.add(beanFactory.getBean(name, BeanDefinitionRegistryPostProcessor.class));
}
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
for (BeanDefinitionRegistryPostProcessor processor : processors) {
//执行BeanDefinition注册相关操作
processor.postProcessBeanDefinitionRegistry(registry);
processor.postProcessBeanFactory(beanFactory);
}
}
}
第 6 步,将已加载的 BeanPostProcessor
注册到 BeanFactory
中。具体过程由 PostProcessorRegistrationDelegate
的静态方法实现,逻辑较为简单,请自行查看代码。
//step-6 注册BeanPostProcessor
protected void registerBeanPostProcessors(ConfigurableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory);
}
第 7 步,注册事件多播器组件,先略过,稍后在事件机制中介绍。
第 8 步,onRefresh
方法是空实现,注意与第 4 步的区别。此时 BeanFactory
和容器的相关配置已经完成,子类可以进行一些自定义的操作。比如 EmbeddedWebApplicationContext
完成了内嵌 servlet 容器的创建,示例代码如下:
public class EmbeddedWebApplicationContext extends GenericApplicationContext implements WebApplicationContext {
private volatile EmbeddedServletContainer embeddedServletContainer;
@Override
protected void onRefresh() {
super.onRefresh();
//创建嵌入式Servlet容器
createEmbeddedServletContainer();
}
private void createEmbeddedServletContainer() {
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
}
}
4.3 收尾工作
第 9 步,将事件监听器添加到事件多播器中,先略过,稍后在事件机制中介绍。
第 10 步,BeanFactory
的初始化已完成,这里添加了一个 StringValueResolver
的匿名类,用于解析 @Value
注解。之前在讲解依赖注入时,在测试代码中也是这样使用的,现在我们知道这一功能是由AbstractApplicationCotext
提供的。此外,还调用了 BeanFactory
的 preInstantiateSingletons
方法,确保所有的单例都被创建。
//step-10 BeanFactory的最后操作,实例化所有的单例Bean
protected void finishBeanFactoryInitialization(ConfigurableBeanFactory beanFactory) {
//1) 注册字符串解析器,用于解析@Value注解
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
@Override
public String resolveStringValue(String strVal) {
return getEnvironment().resolvePlaceholders(strVal);
}
});
}
//2) 实例化所有的单例Bean
beanFactory.preInstantiateSingletons();
}
第 11 步,初始化 Lifecycle
相关组件,并发送容器刷新完成的事件。这里先略过,Lifecycle
组件和事件机制在后面章节中讲解。
//step-11 收尾工作
protected void finishRefresh() {
//初始化LifecycleProcessor并启动生命周期组件(TODO)
//发送ContextRefreshedEvent事件(TODO)
}
5. 测试
测试方法比较简单,创建了一个 Spring 容器,向里边注册 BeanDefinition
,最终拿到相应的实例。需要注意的是 registerBeanDefinition
和 getBean
方法是通过 ApplicationContext
实例调用的,这说明对于装饰模式的核心功能来说,不管是组件类还是装饰类调用,对于外界都是透明的。
//测试方法
@Test
public void testSimpleContext() {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBeanDefinition("user", new RootBeanDefinition(User.class));
context.refresh(); //刷新容器
User user = context.getBean("user", User.class);
System.out.println("ApplicationContext简单实现:" + user);
}
从测试结果可以看到,通过 ApplicationContext
同样可以完成注册 BeanDefinition
以及获取单例的操作。
ApplicationContext简单实现:basic.User@4563e9ab
6. 总结
本节我们梳理了 context 模块涉及的 ApplicationContext
的继承结构,包括两个接口,两个抽象类和两个实现类。其中 AbstractApplicationContext
是核心类,不仅拥有一系列属性,还定义了 refresh
方法完成了容器的初始化,其他实现类的构造函数都需要调用 refresh
方法,才能使得容器变得可用。
我们着重分析了 refresh
方法的实现原理,将复杂的流程分为三个部分,首先是准备一个可用的 BeanFactory
实例,然后是对容器设置必要的组件,最后确保所有的 Bean 都被实例化,以及其他的收尾工作。如此一来,我们实现了一个最简单的 ApplicationContext
容器。由于装饰模式的作用,我们将原先由组件类 BeanFactory
完成的功能,转交给装饰类 ApplicationContext
同样可以完成。
7. 项目结构
新增修改一览,新增(13),修改(0)。
context
├─ src
│ ├─ main
│ │ └─ java
│ │ └─ cn.stimd.spring.context
│ │ ├─ support
│ │ │ ├─ AbstractApplicationContext.java (+)
│ │ │ ├─ AbstractRefreshableApplicationContext.java (+)
│ │ │ ├─ ApplicationContextAwareProcessor.java (+)
│ │ │ ├─ GenericApplicationContext.java (+)
│ │ │ └─ PostProcessorRegistrationDelegate.java (+)
│ │ ├─ ApplicationContext.java (+)
│ │ ├─ ApplicationContextAware.java (+)
│ │ ├─ ConfigurableApplicationContext.java (+)
│ │ ├─ EnvironmentAware.java (+)
│ │ └─ ResourceLoaderAware.java (+)
│ └─ test
│ └─ java
│ └─ context
│ └─ basic
│ ├─ BasicTest.java (+)
│ └─ User.java (+)
└─ pom.xml (+)
注:+号表示新增、*表示修改
注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。
欢迎关注公众号【Java编程探微】,加群一起讨论。
原创不易,觉得内容不错请关注、点赞、收藏。
转载自:https://juejin.cn/post/7399481687656251407