发布时间:2025-12-09 16:53:07 浏览次数:5
用过spring的小伙伴想必都知道IOC容器吧,spring将我们的单例对象实例化后保存到IOC容器,而且一说到IOC容器,大家第一反应都是ApplicationContext,读过源码的同学还知道IOC容器中的单例对象都是保存在一个Map集合singletonObjects中的,而且是以beanName为key,单例对象为value的Map集合。
但是目前大家用的肯定都是springboot了,如果面试官问你springboot中的IOC容器是什么?而你还像上面那样照本宣科按照spring的回答,那你就错了。
正解:在springboot刚启动时,IOC容器为BootstrapContext,直到ApplicationContext初始化完成时,IOC容器为ApplicationContext。
是的,springboot有不仅一个ApplicationContextIOC容器,他还有另外一个IOC容器,即BootstrapContext。
对标配置文件application.yml和bootstrap.yml,不难知道ApplicationContext和BootstrapContext的关系:
与ApplicationContextIOC容器相同的是,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@FunctionalInterfaceinterface 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>() {@Overridepublic T get(BootstrapContext context) {return parent.get(context);}@Overridepublic 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}}看到这里,我们应该对这两个接口有个基本了解
那具体是怎么实现的呢?下面登场的就是他们的实现类DefaultBootstrapContext
先了解一下他的UML图
其中ConfigurableBootstrapContext接口继承自上面两个接口,它没有定义任何扩展的方法,仅仅是对获取实例和注册实例的汇总。
因此我们可以认为,DefaultBootstrapContext就是一个支持注册和获取的IOC容器
public class DefaultBootstrapContext implements ConfigurableBootstrapContext {// 保存单例对象的bean工厂InstanceSupplierprivate 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 {@Overridepublic void intitialize(BootstrapRegistry registry) {System.out.println("向BootStrapContext中注册bean");// 向bootstrapContext容器中注册组件AComponentA componentA = new ComponentA();System.out.println("componentA:" + componentA);registry.register(ComponentA.class, (bootstrapContext) -> componentA);// 向bootstrapContext容器中注册组件BComponentB 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的方法@Overridepublic 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的方法@Overridepublic 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> {@Overridepublic void onApplicationEvent(BootstrapContextClosedEvent event) {System.out.println("================即将关闭BootStrapContext================");// 从BootStrapContext容器中获取componentA和componentBBootstrapContext 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");}}在前面的引导类ComponentInit中,我们已经将ComponetA 和 ComponentB注册到BootstrapContext容器中了,现在需要向该容器中注册监听器
public class ComponentInit implements Bootstrapper {@Overridepublic 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供项目使用。
纸上得来终觉浅,绝知此事要躬行。
——————我是万万岁,我们下期再见——————