一、前言
用过spring的小伙伴想必都知道IOC容器吧,spring将我们的单例对象实例化后保存到IOC容器,而且一说到IOC容器,大家第一反应都是ApplicationContext
,读过源码的同学还知道IOC容器中的单例对象都是保存在一个Map集合singletonObjects
中的,而且是以beanName为key,单例对象为value的Map集合。
但是目前大家用的肯定都是springboot了,如果面试官问你springboot中的IOC容器是什么?而你还像上面那样照本宣科按照spring的回答,那你就错了。
正解:在springboot刚启动时,IOC容器为BootstrapContext
,直到ApplicationContext
初始化完成时,IOC容器为ApplicationContext
。
是的,springboot有不仅一个ApplicationContext
IOC容器,他还有另外一个IOC容器,即BootstrapContext
。
对标配置文件application.yml
和bootstrap.yml
,不难知道ApplicationContext
和BootstrapContext
的关系:
ApplicationContext
是常规的IOC容器BootstrapContext
是项目启动前的IOC容器
与ApplicationContext
IOC容器相同的是,BootstrapContext
容器内部也是通过一个Map集合保存单例对象的。但不同的是,在这个Map集合中是以单例对象的类型为key,单例对象为value来保存的,也正是这个原因,所以一定是单例对象。
二、介绍
BootstrapContext是一个引导上下文。我们知道ApplicationContext
是在项目启动过程中完成初始化、注册bean等一系列操作的,但BootstrapContext
是在项目启动前就完成了。另外在BootstrapContext
保存的不是真实的单例对象,而是该单例对象的Bean工厂(InstanceSupplier),当我们第一次获取单例对象时都是从bean工厂中获取的(InstanceSupplier)。下面是spring官方对他的解释:
一个简单的对象注册表,在启动和环境后处理期间可用,直到准备好ApplicationContext为止。 可用于注册创建成本较高的实例,或者在ApplicationContext可用之前需要共享的实例。 注册表使用Class作为键,这意味着只能存储给定类型的单个实例。 addCloseListener(ApplicationListener)方法可用于添加一个侦听器,该侦听器可以在BootstrapContext已关闭且ApplicationContext已完全准备就绪时执行操作。例如,实例可以选择将自己注册为常规Spring bean,以便应用程序可以使用它。
三、源码
1. BootstrapContext容器
BootstrapContext是一个IOC容器,我们看一下它的小白脸长啥样
public interface BootstrapContext {
// 从容器中获取单例对象
<T> T get(Class<T> type) throws IllegalStateException;
// 从容器中获取单例对象,如果不存在,则返回other
<T> T getOrElse(Class<T> type, T other);
// 从容器中获取单例对象,如果不存在,则从other中获取,可以认为它是一个FactoryBean
<T> T getOrElseSupply(Class<T> type, Supplier<T> other);
// 从容器中获取单例对象,如果不存在,则从exceptionSupplier中获取一个异常抛出来
<T, X extends Throwable> T getOrElseThrow(Class<T> type, Supplier<? extends X> exceptionSupplier) throws X;
// 判断指定类型是否已注册到容器
<T> boolean isRegistered(Class<T> type);
}
我们看到BootstrapContext
容器只定义了获取对象的方法,那如何向容器中注册呢?这个任务交给BootstrapRegistry
。
2. BootstrapRegistry注册器
BootstrapRegistry是用来向BootstrapContext
容器中注册单例对象的注册器,看一下它的小屁股长啥样
public interface BootstrapRegistry {
// 向容器中注册实例,如果容器中已存在该类型对应的实例,会发生覆盖
<T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier);
// 向容器中注册实例,如果容器中已存在该类型对应的实例,则取消注册,即不发生覆盖
<T> void registerIfAbsent(Class<T> type, InstanceSupplier<T> instanceSupplier);
// 判断指定类型是否已注册到容器
<T> boolean isRegistered(Class<T> type);
// 从容器中获取指定类型对应的InstanceSupplier实例
<T> InstanceSupplier<T> getRegisteredInstanceSupplier(Class<T> type);
// 添加BootstrapContextClosedEvent事件的监听器
void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener);
// 可理解为FactoryBean
@FunctionalInterface
interface InstanceSupplier<T> {
T get(BootstrapContext context);
default Scope getScope() {
return Scope.SINGLETON;
}
default InstanceSupplier<T> withScope(Scope scope) {
Assert.notNull(scope, "Scope must not be null");
InstanceSupplier<T> parent = this;
return new InstanceSupplier<T>() {
@Override
public T get(BootstrapContext context) {
return parent.get(context);
}
@Override
public Scope getScope() {
return scope;
}
};
}
// 将instance实例放在InstanceSupplier中
static <T> InstanceSupplier<T> of(T instance) {
return (registry) -> instance;
}
static <T> InstanceSupplier<T> from(Supplier<T> supplier) {
return (registry) -> (supplier != null) ? supplier.get() : null;
}
}
enum Scope {
// 单例
SINGLETON,
// 原型
PROTOTYPE
}
}
看到这里,我们应该对这两个接口有个基本了解
BootstrapContext
定义了从容器中获取实例的方法BootstrapRegistry
定义了向容器中注册实例的方法。
那具体是怎么实现的呢?下面登场的就是他们的实现类DefaultBootstrapContext
3. DefaultBootstrapContext
先了解一下他的UML图
其中ConfigurableBootstrapContext
接口继承自上面两个接口,是对获取实例和注册实例的汇总,但它没有做出更多的扩展。实现类DefaultBootstrapContext
是springboot提供的默认的唯一的bootstrapIOC容器。他对IOC容器实现了所有获取bean、注册bean以及其他相关的方法。
public class DefaultBootstrapContext implements ConfigurableBootstrapContext {
// InstanceSupplier调用get方法得到的对象将放在instances集合中
private final Map<Class<?>, InstanceSupplier<?>> instanceSuppliers = new HashMap<>();
private final Map<Class<?>, Object> instances = new HashMap<>();
// 事件广播器,将事件广播出去后,监听该事件的监听器将做出反应
private final ApplicationEventMulticaster events = new SimpleApplicationEventMulticaster();
@Override
public <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier) {
register(type, instanceSupplier, true);
}
@Override
public <T> void registerIfAbsent(Class<T> type, InstanceSupplier<T> instanceSupplier) {
register(type, instanceSupplier, false);
}
private <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier, boolean replaceExisting) {
Assert.notNull(type, "Type must not be null");
Assert.notNull(instanceSupplier, "InstanceSupplier must not be null");
synchronized (this.instanceSuppliers) {
boolean alreadyRegistered = this.instanceSuppliers.containsKey(type);
if (replaceExisting || !alreadyRegistered) {
Assert.state(!this.instances.containsKey(type), () -> type.getName() + " has already been created");
this.instanceSuppliers.put(type, instanceSupplier);
}
}
}
@Override
public <T> boolean isRegistered(Class<T> type) {
synchronized (this.instanceSuppliers) {
return this.instanceSuppliers.containsKey(type);
}
}
@Override
@SuppressWarnings("unchecked")
public <T> InstanceSupplier<T> getRegisteredInstanceSupplier(Class<T> type) {
synchronized (this.instanceSuppliers) {
return (InstanceSupplier<T>) this.instanceSuppliers.get(type);
}
}
@Override
public void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener) {
this.events.addApplicationListener(listener);
}
@Override
public <T> T get(Class<T> type) throws IllegalStateException {
return getOrElseThrow(type, () -> new IllegalStateException(type.getName() + " has not been registered"));
}
@Override
public <T> T getOrElse(Class<T> type, T other) {
return getOrElseSupply(type, () -> other);
}
@Override
public <T> T getOrElseSupply(Class<T> type, Supplier<T> other) {
synchronized (this.instanceSuppliers) {
InstanceSupplier<?> instanceSupplier = this.instanceSuppliers.get(type);
return (instanceSupplier != null) ? getInstance(type, instanceSupplier) : other.get();
}
}
@Override
public <T, X extends Throwable> T getOrElseThrow(Class<T> type, Supplier<? extends X> exceptionSupplier) throws X {
synchronized (this.instanceSuppliers) {
InstanceSupplier<?> instanceSupplier = this.instanceSuppliers.get(type);
if (instanceSupplier == null) {
throw exceptionSupplier.get();
}
return getInstance(type, instanceSupplier);
}
}
@SuppressWarnings("unchecked")
private <T> T getInstance(Class<T> type, InstanceSupplier<?> instanceSupplier) {
T instance = (T) this.instances.get(type);
if (instance == null) {
instance = (T) instanceSupplier.get(this);
if (instanceSupplier.getScope() == Scope.SINGLETON) {
this.instances.put(type, instance);
}
}
return instance;
}
/**
* Method to be called when {@link BootstrapContext} is closed and the
* {@link ApplicationContext} is prepared.
* @param applicationContext the prepared context
*/
public void close(ConfigurableApplicationContext applicationContext) {
this.events.multicastEvent(new BootstrapContextClosedEvent(this, applicationContext));
}
}
从源码中我们也可以看到,在容器中无论获取bean还是注册bean,都使用了synchronized
修饰的代码块,因此该IOC容器还是线程安全的。
讲了上面两个接口一个实现类后,我们对该IOC容器的原理已经有了认识,但如何使用呢?
使用
其中ConfigurableBootstrapContext
接口继承自上面两个接口,它没有定义任何扩展的方法,仅仅是对获取实例和注册实例的汇总。
因此我们可以认为,DefaultBootstrapContext
就是一个支持注册和获取的IOC容器
public class DefaultBootstrapContext implements ConfigurableBootstrapContext {
// 保存单例对象的bean工厂InstanceSupplier
private final Map<Class<?>, InstanceSupplier<?>> instanceSuppliers = new HashMap<>();
// 保存单例对象。采用惰性思想,当首次访问单例对象时,从InstanceSupplier获取并保存
private final Map<Class<?>, Object> instances = new HashMap<>();
// 监听器集合
private final ApplicationEventMulticaster events = new SimpleApplicationEventMulticaster();
}
四、实践
对BootstrapContext
容器的源码及其作用有了了解后,我们通过demo对其进行演示。
1. 定义项目启动前需要处理的方法
新建两个类:ComponentA
和 ComponentB
,我们需要在项目启动前,让这两个类做一些事情,如step1()
、step2()
public class ComponentA {
public ComponentA() {
System.out.println("ComponentA实例化");
}
public void step1() {
System.out.println("ComponentA.step1()");
}
public void step2() {
System.out.println("ComponentA.step2()");
}
}
public class ComponentB {
public ComponentB() {
System.out.println("ComponentB实例化");
}
public void step1() {
System.out.println("ComponentB.step1()");
}
public void step2() {
System.out.println("ComponentB.step2()");
}
}
2. 定义引导类,注册组件到Bootstrap容器
新建一个类:ComponentInit
,并实现Bootstrapper
接口中的intitialize()
方法,将我们定义的ComponentA
和 ComponentB
注册到容器中。
public class ComponentInit implements Bootstrapper {
@Override
public void intitialize(BootstrapRegistry registry) {
System.out.println("向BootStrapContext中注册bean");
// 向bootstrapContext容器中注册组件A
ComponentA componentA = new ComponentA();
System.out.println("componentA:" + componentA);
registry.register(ComponentA.class, (bootstrapContext) -> componentA);
// 向bootstrapContext容器中注册组件B
ComponentB componentB = new ComponentB();
System.out.println("componentB:" + componentB);
registry.register(ComponentB.class, (bootstrapContext) -> componentB);
}
}
3. 定义项目启动监听器
新建一个监听器,并实现SpringApplicationRunListener
接口。该接口定义了项目启动前的一些过程如:项目开始启动、环境准备就绪,
public class BootStrapContextListener implements SpringApplicationRunListener {
public BootStrapContextListener(SpringApplication springApplication, String[] strings) {
}
// 当项目开始启动时,执行ComponentA和ComponentB的方法
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
System.out.println("================项目启动================");
ComponentA componentA = bootstrapContext.get(ComponentA.class);
componentA.step1();
ComponentB componentB = bootstrapContext.get(ComponentB.class);
componentB.step1();
}
// 当环境准备就绪时,执行ComponentA和ComponentB的方法
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
System.out.println("================环境准备就绪================");
ComponentA componentA = bootstrapContext.get(ComponentA.class);
componentA.step2();
ComponentB componentB = bootstrapContext.get(ComponentB.class);
componentB.step2();
}
}
4. 注册引导类
在resources
目录下新建META-INF/spring.factories
将引导类注册到该文件中
# 注册引导类
org.springframework.boot.Bootstrapper = \
com.idealhooray.bootstrapContext.bootstrappers.ComponentInit
# 注册项目启动监听器
org.springframework.boot.SpringApplicationRunListener = \
com.idealhooray.bootstrapContext.listeners.BootStrapContextListener
5. 启动项目
运行项目的main()
方法,可以看到,在打印横幅前,我们定义的两个组件ComponentA
和 ComponentB
的方法已经完成实例化并执行方法了。
6. 将Bootstrap容器中的实例实例作为bean注册到ApplicationContext容器中
在spring的设计中,当ApplicationContext容器完成初始化后,Bootstrap容器需要调用close()
方法进行关闭,并且在关闭时发布一个事件BootstrapContextClosedEvent
来表示将要关闭Bootstrap容器了。因此我们可以定义一个监听器,当监听到事件BootstrapContextClosedEvent
时,将Bootstrap容器中的单例注册到ApplicationContext容器中。
新建一个监听BootstrapContextClosedEvent
事件的监听器类BootStrapContextClosedListener
,实现ApplicationListener
接口。
public class BootStrapContextClosedListener implements ApplicationListener<BootstrapContextClosedEvent> {
@Override
public void onApplicationEvent(BootstrapContextClosedEvent event) {
System.out.println("================即将关闭BootStrapContext================");
// 从BootStrapContext容器中获取componentA和componentB
BootstrapContext bootstrapContext = event.getBootstrapContext();
ComponentA componentA = bootstrapContext.get(ComponentA.class);
ComponentB componentB = bootstrapContext.get(ComponentB.class);
System.out.println("关闭bootstrapContext");
// 将componentA和componentB注册到applicationContext容器
ConfigurableApplicationContext applicationContext = event.getApplicationContext();
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
beanFactory.registerSingleton("componentA", componentA);
beanFactory.registerSingleton("componentB", componentB);
// 验证是否注册成功
Object beanA = beanFactory.getBean("componentA");
Object beanB = beanFactory.getBean("componentB");
if (!ObjectUtils.isEmpty(beanA) && !ObjectUtils.isEmpty(beanB)) {
System.out.println("beanA 和 beanB 已成功注册到ApplicationContext IOC容器");
// 判断applicationContext容器中的beanA和beanB 是否和 BootStrapContext容器中的componentA和componentB 为同一个对象
System.out.println("componentA:" + beanA);
System.out.println("componentB:" + beanB);
}
System.out.println("启动applicationContext");
}
}
7. 注册BootstrapContextClosedEvent
事件的监听器
在前面的引导类ComponentInit
中,我们已经将ComponetA
和 ComponentB
注册到BootstrapContext容器中了,现在需要向该容器中注册监听器
public class ComponentInit implements Bootstrapper {
@Override
public void intitialize(BootstrapRegistry registry) {
// ...
// 注册监听器
registry.addCloseListener(new BootStrapContextClosedListener());
}
}
8. 启动项目
运行项目的main()
方法,可以看到,在打印横幅前,我们定义的两个组件ComponentA
和 ComponentB
的方法已经完成实例化并执行方法了。在打印横幅后,这时applicationContext
容器已经完成了初始化,我们可以看到beanA
和 beanB
已成功注册到ApplicationContext
IOC容器中了,而且从打印出的内存地址来看beanA和ComponentA是同一个对象,beanB和ComponentB是同一个对象,完全正确。
五、为什么这么做?
为什么?当然是读源码知道的。
1. 为什么定义实现Bootstrapper接口的引导类
在springboot启动过程中,会获取所有Bootstrapper
接口实现类的集合。
然后通过Bootstrapper
接口实现类的intitialize()
方法,对BootstrapContext容器进行初始化
2. 为什么要将引导类注册到spring.factories文件中
如上图,在获取所有Bootstrapper
接口实现类的集合时,是通过getSpringFactoriesInstances()
方法获取的。而在该方法中,会从spring.factories
文件中获取所有的Bootstrapper
接口实现类。
而FACTORIES_RESOURCE_LOCATION
变量即为META-INF/spring.factories
.
3. 为什么定义BootStrapContextClosedListener监听器
在springboot项目启动过程中,会对ApplicationContext
容器执行大量的代码逻辑,其中在做准备工作时,调用BootstrapContext
的close()
方法将其关闭,而在关闭BootstrapContext
的方法中,发布BootstrapContextClosedEvent
事件,具体操作由该事件对应的监听器执行。
在关闭容器时发布事件
而我们定义的监听器正好监听的就是这个事件。
六、总结
在ApplicationContext
初始化前,通过BootstrapContext
创建一些组件实例(需要的话),等ApplicationContext
初始化后,再将BootstrapContext
创建创建的实例注册到ApplicationContext
中作为常规的bean供项目使用。
纸上得来终觉浅,绝知此事要躬行。
————————我是万万岁,我们下期再见————————