用过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,以便应用程序可以使用它。
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
。
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
先了解一下他的UML图
其中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对其进行演示。
新建两个类: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()");
}
}
新建一个类: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);
}
}
新建一个监听器,并实现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();
}
}
在resources
目录下新建META-INF/spring.factories
将引导类注册到该文件中
# 注册引导类
org.springframework.boot.Bootstrapper = \
com.idealhooray.bootstrapContext.bootstrappers.ComponentInit
# 注册项目启动监听器
org.springframework.boot.SpringApplicationRunListener = \
com.idealhooray.bootstrapContext.listeners.BootStrapContextListener
运行项目的main()
方法,可以看到,在打印横幅前,我们定义的两个组件ComponentA
和 ComponentB
的方法已经完成实例化并执行方法了。
在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");
}
}
BootstrapContextClosedEvent
事件的监听器在前面的引导类ComponentInit
中,我们已经将ComponetA
和 ComponentB
注册到BootstrapContext容器中了,现在需要向该容器中注册监听器
public class ComponentInit implements Bootstrapper {
@Override
public void intitialize(BootstrapRegistry registry) {
// ...
// 注册监听器
registry.addCloseListener(new BootStrapContextClosedListener());
}
}
运行项目的main()
方法,可以看到,在打印横幅前,我们定义的两个组件ComponentA
和 ComponentB
的方法已经完成实例化并执行方法了。在打印横幅后,这时applicationContext
容器已经完成了初始化,我们可以看到beanA
和 beanB
已成功注册到ApplicationContext
IOC容器中了,而且从打印出的内存地址来看beanA和ComponentA是同一个对象,beanB和ComponentB是同一个对象,完全正确。
为什么?当然是读源码知道的。
在springboot启动过程中,会获取所有Bootstrapper
接口实现类的集合。
然后通过Bootstrapper
接口实现类的intitialize()
方法,对BootstrapContext容器进行初始化
如上图,在获取所有Bootstrapper
接口实现类的集合时,是通过getSpringFactoriesInstances()
方法获取的。而在该方法中,会从spring.factories
文件中获取所有的Bootstrapper
接口实现类。
而FACTORIES_RESOURCE_LOCATION
变量即为META-INF/spring.factories
.
在springboot项目启动过程中,会对ApplicationContext
容器执行大量的代码逻辑,其中在做准备工作时,调用BootstrapContext
的close()
方法将其关闭,而在关闭BootstrapContext
的方法中,发布BootstrapContextClosedEvent
事件,具体操作由该事件对应的监听器执行。
在关闭容器时发布事件
而我们定义的监听器正好监听的就是这个事件。
在ApplicationContext
初始化前,通过BootstrapContext
创建一些组件实例(需要的话),等ApplicationContext
初始化后,再将BootstrapContext
创建创建的实例注册到ApplicationContext
中作为常规的bean供项目使用。
纸上得来终觉浅,绝知此事要躬行。
——————我是万万岁,我们下期再见——————