• Springboot启动流程分析(四):完成启动流程


    目录

    一 添加BeanPostProcessors到IOC容器

    二 国际化支持

    三 初始化监听器的多路播放器

    四 刷新容器

    五 注册监听器到IOC容器的多播器

    六 完成bean的大规模实例化

     6.1 大规模实例化bean

    6.1.1 连续三层do...while循环作用

    6.1.2 FactoryBean是什么?为什么要执行2次getBean方法?

    七 完成IOC刷新

    八 完成IOC刷新

    九 结束Springboot启动流程

    十 总结


    在Springboot的启动过程中,当执行到

    AbstractApplicationContext

    的refresh方法时,当执行完方法:

    this.invokeBeanFactoryPostProcessors(beanFactory);

    一 添加BeanPostProcessors到IOC容器

    在执行完所有的BeanFactoryPostProcessors指定方法之后。下一步,会把BeanPostProcessors,也就是beanFacotry中已经注册过bean定义信息的,所有bean的后置处理器,都加入到beanFacotry的集合属性——beanPostProcessors中。

    其具体执行方法为:

    this.registerBeanPostProcessors(beanFactory);

    接着再调用 :

    ostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);

    开始执行真正的bean后置处理器加载步骤。

    其核心步骤,就是通过beanFactory的getBeanNamesForType方法,去beanFacotry的属性——beanDefinitionNames集合中,筛选出实现了BeanPostProcessors接口的所有beanNames,返回一个String数组:

    String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

     至此,相当于大概获取了beanFacroty中的全部bean的后置处理器的beanName信息了。

    为什么说是大概呢?因为接下来,还需要手动加入一个bean的后置处理器——BeanPostProcessorChecker:

    1. int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
    2. beanFactory.addBeanPostProcessor(new PostProcessorRegistrationDelegate.BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

    顾名思义,应该是对bean的后置处理器进行一些统计和检查。

    接下来,就是把获取到的postProcessorNames根据属性进行分类,并根据分类的顺序,依次添加到beanFactory的集合属性——beanPostProcessors中。

    其分类和添加到beanPostProcessors集合的顺序依次为:

    priorityOrderedPostProcessors集合、 
    orderedPostProcessorNames集合、 
    nonOrderedPostProcessorNames集合、 
    internalPostProcessors集合 
    

     其主要是通过接口PriorityOrdered、Ordered、MergedBeanDefinitionPostProcessor来判断应该把后置处理器放入哪个集合中。

    在最后,还需要添加一个指定后置处理器:

    beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));

    其实这个执行过程和上一篇文章,beanFacotry后置处理器的过程有很多相似之处,但是相对来说又简单了很多。

    二 国际化支持

    this.initMessageSource();

    国际化的源码其实更简单,就是看容器中是否有名为messageSource的bean实例,如果有则直接赋值给AbstractApplicationContext中messageSource的属性。

    如果找不到,则通过DelegatingMessageSource实例化一个messageSource实例,放入容器中。

    这意味着,我们可以通过配置类,自定义一个messageSource实例:

    1. @Configuration
    2. public class MessageConfig {
    3. @Bean(name = "messageSource")
    4. public MessageSource getMessageSource() {
    5. ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    6. messageSource.setDefaultEncoding("GBK");
    7. messageSource.addBasenames("message");
    8. return messageSource;
    9. }
    10. }

    其具体用法为:

    1. @Override
    2. public void run(String... args) throws Exception {
    3. String message = messageSource.getMessage("user.name", null, null, Locale.ENGLISH);
    4. String messageCn = messageSource.getMessage("user.name", null, null, Locale.CHINA);
    5. System.out.println(message);
    6. System.out.println(messageCn);
    7. System.out.println("完成国际化测试");
    8. }

    其实就是对resource文件夹下面properties文件的一次封装:

    三 初始化监听器的多路播放器

    this.initApplicationEventMulticaster();

    单看代码这个其实也很简单,就是判断IOC容器中是否有applicationEventMulticaster这个实例,如果有,赋值给当前类AbstractApplicationContext的属性——applicationEventMulticaster。如果没有,就new一个多播器实例——SimpleApplicationEventMulticaster,赋值后,再注入到容器中。

    1. protected void initApplicationEventMulticaster() {
    2. ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
    3. if (beanFactory.containsLocalBean("applicationEventMulticaster")) {
    4. this.applicationEventMulticaster = (ApplicationEventMulticaster)beanFactory.getBean("applicationEventMulticaster", ApplicationEventMulticaster.class);
    5. ......
    6. } else {
    7. this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
    8. beanFactory.registerSingleton("applicationEventMulticaster", this.applicationEventMulticaster);
    9. ......

    这里需要注意的是,这里的多播器其实就是前面Springboot启动类——SpringApplication的run方法中监听器所使用的多播器,只不过前面利用EventPublishingRunListener类包装了一层,同样也是把SimpleApplicationEventMulticaster作为属性放入到了EventPublishingRunListener中:

    1. public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    2. private final SpringApplication application;
    3. private final String[] args;
    4. private final SimpleApplicationEventMulticaster initialMulticaster;
    5. public EventPublishingRunListener(SpringApplication application, String[] args) {
    6. this.application = application;
    7. this.args = args;
    8. this.initialMulticaster = new SimpleApplicationEventMulticaster();
    9. Iterator var3 = application.getListeners().iterator();
    10. while(var3.hasNext()) {
    11. ApplicationListener listener = (ApplicationListener)var3.next();
    12. this.initialMulticaster.addApplicationListener(listener);
    13. }
    14. }

    唯一的不同就是EventPublishingRunListener在Springboot启动结束之后,如果不再被引用,会被垃圾回收器回收,但是容器中beanName为applicationEventMulticaster的实例会一直存在,可以在项目启动后,在任意时候从容器中获取。

    四 刷新容器

    this.onRefresh();

    第一部分是针对SpringMVC层面的主题控制,现在项目通常使用前后端分离,所以用处不大。

    其提供的自定义功能,也主要是通过自定义beanName——themeSource来实现:

    1. public static ThemeSource initThemeSource(ApplicationContext context) {
    2. if (context.containsLocalBean("themeSource")) {
    3. ThemeSource themeSource = (ThemeSource)context.getBean("themeSource", ThemeSource.class);
    4. ......

    但是这一步,还有一个最重要的功能,会启动Spring内置的tomcat容器,并通过while(true)的形式,来接收所有的Servlet请求。

    五 注册监听器到IOC容器的多播器

    this.registerListeners();

    这里的监听器分为两块:

    1. //启动过程中加入的监听器
    2. Iterator var1 = this.getApplicationListeners().iterator();
    3. while(var1.hasNext()) {
    4. ApplicationListener listener = (ApplicationListener)var1.next();
    5. //以监听器的方式加入多播器
    6. this.getApplicationEventMulticaster().addApplicationListener(listener);
    7. }
    8. //实现接口ApplicationListener的监听器,多用于自定义监听器
    9. String[] listenerBeanNames = this.getBeanNamesForType(ApplicationListener.class, true, false);
    10. String[] var7 = listenerBeanNames;
    11. int var3 = listenerBeanNames.length;
    12. for(int var4 = 0; var4 < var3; ++var4) {
    13. String listenerBeanName = var7[var4];
    14. //以beanName的方式加入多播器 this.getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    15. }

    一块是SpringApplication在执行run方法的过程中,不断加入IOC容器中的监听器,相当于系统自带监听器(也可以自定义),另一块是需要从IOC中获取实现了接口ApplicationListener的监听器,我们如果要实现自定义的监听器,可以直接实现这个接口,然后把监听器注入IOC容器,就可以被IOC的多播器来执行了:

    1. public interface ApplicationListenerextends ApplicationEvent> extends EventListener {
    2. void onApplicationEvent(E var1);
    3. }

    注意只是加入了IOC容器,而监听器是要依靠IOC多播器去实现的,由于前面才刚把IOC自己的多播器实例化,所以此时也才能往多播器中添加监听器。

    在加入两种类型的监听器以后,利用IOC的多播器执行一个指定集合——earlyApplicationEvents中的所有监听器方法:

    1. Set earlyEventsToProcess = this.earlyApplicationEvents;
    2. this.earlyApplicationEvents = null;
    3. if (earlyEventsToProcess != null) {
    4. Iterator var9 = earlyEventsToProcess.iterator();
    5. while(var9.hasNext()) {
    6. ApplicationEvent earlyEvent = (ApplicationEvent)var9.next();
    7. this.getApplicationEventMulticaster().multicastEvent(earlyEvent);
    8. }

    六 完成bean的大规模实例化

    this.finishBeanFactoryInitialization(beanFactory);

    在开始bean的大规模实例化之前,还需要做几个准备工作。

    首先设置IOC中的类型转换器——ConversionService:

    1. if (beanFactory.containsBean("conversionService") && beanFactory.isTypeMatch("conversionService", ConversionService.class)) {
    2. beanFactory.setConversionService((ConversionService)beanFactory.getBean("conversionService", ConversionService.class));
    3. }

    再判断容器中是否有配置文件解析器,如果没有,利用Environment的文件解析器:

    1. if (!beanFactory.hasEmbeddedValueResolver()) {
    2. beanFactory.addEmbeddedValueResolver((strVal) -> {
    3. return this.getEnvironment().resolvePlaceholders(strVal);
    4. });
    5. }

    实例化接口——LoadTimeWeaverAware的实现类:

    1. String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
    2. String[] var3 = weaverAwareNames;
    3. int var4 = weaverAwareNames.length;
    4. for(int var5 = 0; var5 < var4; ++var5) {
    5. String weaverAwareName = var3[var5];
    6. this.getBean(weaverAwareName);
    7. }

    释放类加载器——tempClassLoader:

    beanFactory.setTempClassLoader((ClassLoader)null);

    冻结容器中的所有bean定义信息,不允许有新的bean定义信息注册进来,为大规模实例化bean做准备:

    beanFactory.freezeConfiguration();

     6.1 大规模实例化bean

    beanFactory.preInstantiateSingletons();

    由于preInstantiateSingletons这个方法写的实在是有点难以理解,所以这部分代码需要从整体层面来理解,这样就不至于过于纠结细节,以至于完全无法抓住重点。

    首先,我们看下这个方法的大概结构: 

    可以看到,其主要功能就是获取所有的bean定义信息,构造成一个List,之后用while(true)的形式,无限循环,用以实例化完所有需要实例化的bean,这是这个方法的本质功能。

    那么什么时候跳出循环呢?

    让我们忽略掉那一层套一层的do...while循环,可以发现在bean定义信息的List迭代完成以后,就会以return的方式,直接结束此次方法的调用。 

    这样我们就大概理解了这个方法的作用,和退出方法的时机。

    再看一下迭代器具体做了什么?

    如果当前beanName对于的bean实例是SmartInitializingSingleton的实现类,则执行其指定的接口方法afterSingletonsInstantiated。

    到这一步,又出现更让人迷惑的一个嵌套,第二个while(true)循环是干嘛的?什么时候退出?

    依旧是需要忽略一些无关的细节,从整体来看:

    可以看到,第二个while(true)循环,跳出的时机是当beanName是FactoryBean的时候,需要跳出当前的while循环,同时也可以看出,如果不是FactoryBean,会直接进行实例化,然后进入当前while(true)循环中的下一次do...while循环,也就是一直进行普通bean的实例化,直到出现FactoryBean,跳出当前while(true)循环。

    跳出去之后呢?别急,外面还有一层while(true)循环等着,此时再结合最外层do...while循环中的条件:

    } while(!(bean instanceof FactoryBean));

    这样do...while方法也跳出去了,FactoryBean类型的bean进入到了最外层while(true)方法的下半部分执行逻辑:

     可以看到,主要是判断当前bean的实例是否是SmartFactoryBean,以及根据它的属性isEagerInit的值,判断是否需要马上实例化beanName。

    至此,preInstantiateSingletons方法的大概逻辑就梳理完了,但是有产生了一些新的问题。

    第一个问题,这个方法中,除了刚才介绍的最外层的do...while循环,在第二层的while(true)循环里面,那3层do...while又分别是做什么的?

    第二个问题,为什么FactoryBean的实例,在前半部分代码中执行了getBean以后,下面如果其isEagerInit属性是true,还要getBean一次,这又是为什么?

    6.1.1 连续三层do...while循环作用

    先从最外层循环开始,首先会执行最外层do块,什么都不做,进入第二层do块,也什么都没做。

    进入第三层do块,获取下一个beanName的bean定义信息,在最内层do块执行完以后,如果当前bean是一个抽象类,继续执行当前最内层do...while循环,获取下一个bean定义信息,如果当前bean不是一个抽象类,跳出最内层do...while。

    执行第二层do...while,嗯,这一层什么都没做,接着判断,如果当前bean定义信息不是一个单例模式(是多态或者其他模式),继续执行最内层do...while循环,获取下一个bean定义信息,此时如果当前bean不是一个抽象类,且是单例模式,才能跳出第二层do...while循环,否则就一直在二三层do...while循环之间,抽取下一个bean定义信息,直到beanName的list中所有的信息都被迭代完,才会结束此方法。

    如果bean的定义信息既不是抽象类,又是一个单例,那么会跳到第一层do...while方法,再判断如果bean的定义信息不是懒加载的方式,会跳出当前的do...while方法,也就是跳出所有的三层do...while,进入下一步。

    否则,就又重新进入第二层do...while,重复上面的方法,获取下一条bean定义信息,直到一个bean既不是抽象类,又是单例,且不是懒加载的定义,才能跳出所有三层do...while循环。

    6.1.2 FactoryBean是什么?为什么要执行2次getBean方法?

    什么是FactoryBean

    FactoryBean又叫做工厂Bean,指用于生产bean的工厂。

    这样说还是有点抽象,通过例子来说明,先看一下FactoryBean的接口信息:

    1. public interface FactoryBean {
    2. String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
    3. @Nullable
    4. T getObject() throws Exception;
    5. @Nullable
    6. Class getObjectType();
    7. default boolean isSingleton() {
    8. return true;
    9. }
    10. }

    可以看到,默认是单例,FactoryBean和我们获取普通的bean的区别在于,需要通过getObject方法来获取真正的bean实例。

    这也是为什么在方法preInstantiateSingletons中,需要通过两次getBean方法,才能真正获取到FactoryBean类型的实例的原因。

    举个例子:

    1. @Component
    2. public class TestFactoryBean implements SmartFactoryBean {
    3. @Override
    4. public Object getObject() throws Exception {
    5. return new Person();
    6. }
    7. @Override
    8. public Class getObjectType() {
    9. return null;
    10. }
    11. @Override
    12. public boolean isEagerInit() {
    13. return true;
    14. }
    15. }

    在Spring容器中,会存在两个实例,一个是通过getBean("&testFactoryBean"),生成的TestFactoryBean实例,一个是通过getBean("testFactoryBean"),生成的Person实例,其实就是通过TestFactoryBean实例的getObject方法,生成的Person实例。

    这也是为什么如果当前bean定义不是懒加载的化,FactoryBean需要通过两次getBean方法生成的原因。

    七 完成IOC刷新

    this.finishRefresh();
    1. protected void finishRefresh() {
    2. //清楚资源缓存
    3. this.clearResourceCaches();
    4. //初始化生命周期处理器
    5. this.initLifecycleProcessor();
    6. //执行生命周期处理器onRefresh方法
    7. this.getLifecycleProcessor().onRefresh();
    8. //发布容器已刷新监听事件
    9. this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this)));
    10. //把当前ioc容器加入集合————applicationContexts
    11. LiveBeansView.registerApplicationContext(this);
    12. }

    至此在AbstractApplicationContext类中,已经执行完了refresh方法中的所有步骤。

    八 完成IOC刷新

    在AbstractApplicationContext完成ioc容器刷新后,需要回到类SpringApplication中,继续执行其run方法,接着执行如下方法:

    this.afterRefresh(context, applicationArguments);

    由于这是一个抽象方法,且没有继承类,略过。

    九 结束Springboot启动流程

    1. //stopWatch记录结束
    2. stopWatch.stop();
    3. if (this.logStartupInfo) {
    4. (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
    5. }
    6. //容器启动完成监听器方法执行
    7. listeners.started(context);
    8. //容器启动后,执行一系列指定Runners
    9. this.callRunners(context, applicationArguments);
    10. } catch (Throwable var10) {
    11. this.handleRunFailure(context, var10, exceptionReporters, listeners);
    12. throw new IllegalStateException(var10);
    13. }
    14. try {
    15. //容器正在运行监听器方法执行
    16. listeners.running(context);
    17. //返回IOC容器,结束方法
    18. return context;

    这里需要注意的是

    this.callRunners(context, applicationArguments);

    其实就是执行容器中所有的ApplicationRunner和CommandLineRunner类型的run方法,其效果和bean实例化过程中的initializeBean方法中执行接口InitializingBean的afterPropertiesSet,是一样的。

    但是其底层的原理完全不一样,一个是bean的实例化过程中执行的,一个是IOC容器启动完成以后回调执行的:

    1. private void callRunners(ApplicationContext context, ApplicationArguments args) {
    2. List runners = new ArrayList();
    3. runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    4. runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    5. AnnotationAwareOrderComparator.sort(runners);
    6. Iterator var4 = (new LinkedHashSet(runners)).iterator();
    7. while(var4.hasNext()) {
    8. Object runner = var4.next();
    9. if (runner instanceof ApplicationRunner) {
    10. this.callRunner((ApplicationRunner)runner, args);
    11. }
    12. if (runner instanceof CommandLineRunner) {
    13. this.callRunner((CommandLineRunner)runner, args);
    14. }
    15. }
    16. }
    17. 十 总结

      至此,我们通过四篇文章,十万余字的代码和文字说明,大概完成了Springboot的全部启动流程,完整而全面的分析了Springboot的全流程。

      但是在完成的过程中,深感对一些核心知识点的介绍还不够深入,后续会以单点的形式对Springboot启动过程中的一些知识点进行针对性梳理,以便能够更好的理解Spring框架。

    18. 相关阅读:
      Java 高级语言特性之 java中的泛型
      GM8775C :是 DSI 转双通道 LVDS发送器
      测试杂谈——一条SQL引发的思考(二)
      了解ARM架构
      智慧导览|智能导游系统|AR景区导览系统|景区电子导览
      SpringBoot Mybatis 多数据源 MySQL+Oracle+Redis
      热修复技术可谓是百花齐放
      SpringBoot、SpringCloud、SpringCloudAlibab对应版本选择
      4项简化IT服务台任务的ChatGPT功能
      如何在IPhone 14、14 Pro和14 Pro Max上添加屏幕锁定
    19. 原文地址:https://blog.csdn.net/bigbearxyz/article/details/127820795