• SpringBoot的启动原理


    SpringBoot整个启动流程分为两个步骤:初始化一个SpringApplication对象、执行该对象的run方法。

    一.初始化SpringApplication对象

    入口程序的run方法会传入启动类的Class对象,run方法中先初始化 一个SpringApplication对象

    我们从启动类开始,来探索一下整个过程的原理

    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    // run方法中先初始化一个SpringApplication对象,然后通过SpringApplication对象调用run方法
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
            return (new SpringApplication(primarySources)).run(args);
    }
    
    • 1
    • 2
    • 3
    • 4
    // 初始化一个SpringApplication对象
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
            this.sources = new LinkedHashSet();
            this.bannerMode = Mode.CONSOLE;
            this.logStartupInfo = true;
            this.addCommandLineProperties = true;
            this.addConversionService = true;
            this.headless = true;
            this.registerShutdownHook = true;
            this.additionalProfiles = new HashSet();
            this.isCustomEnvironment = false;
            this.lazyInitialization = false;
            this.resourceLoader = resourceLoader;
            Assert.notNull(primarySources, "PrimarySources must not be null");
            this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
            this.webApplicationType = WebApplicationType.deduceFromClasspath();
            this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
            this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
            this.mainApplicationClass = this.deduceMainApplicationClass();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    可以看下this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
            ClassLoader classLoader = this.getClassLoader();
            // loadFactoryNames
            Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
            List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
            AnnotationAwareOrderComparator.sort(instances);
            return instances;
     }
    /**
     *loadFactoryNames
     */
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
         String factoryTypeName = factoryType.getName();
         // loadSpringFactories
         return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
     }
    
    
    /** 
     *loadSpringFactories,这里会加载META-INF/spring.factories中的配置类;
     这个方法其实已经将META-INF/spring.factories中所有的配置加载进一个map中了
    */
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
      MultiValueMap<String, String> result = cache.get(classLoader);
      if (result != null) return result;
      try {
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
          URL url = urls.nextElement();
          UrlResource resource = new UrlResource(url);
          Properties properties = PropertiesLoaderUtils.loadProperties(resource);
          for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryTypeName = ((String) entry.getKey()).trim();
            for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
              result.add(factoryTypeName, factoryImplementationName.trim());
            }
          }
        }
        cache.put(classLoader, result);
        return result;
      } catch (IOException ex) {
      ...
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    可以知道初始化流程中最重要的就是通过 SpringFactoriesLoader将spring.factories文件的配置类全部加载到Map<String, List>中,然后找到并设置ApplicationContextInitializer和ApplicationListener两个接口的实现类名称,以便后期构造相应的实例

    - ApplicationContextInitializer的主要目的是在ConfigurableApplicationContext做refresh之前,对ConfigurableApplicationContext实例做进一步的设置或处理。
    - ApplicationListener的目的是Spring框架对Java事件监听机制的一种框架实现。Spring Boot提供两种方式来添加自定义监听器通过SpringApplication.addListeners(ApplicationListener<?>... listeners)或者SpringApplication.setListeners(Collection<? extends ApplicationListener<?>> listeners)两个方法来添加一个或者多个自定义监听器。如果想自定一个监听器,我们需要编写一个监听类实现ApplicationListener,然后还需要在META-INF/spring.factories文件中新增配置即可:
    org.springframework.context.ApplicationListener=\
    cn.zrclass.listeners.xxxxListener\(自定义监听器)
    
    • 1
    • 2
    • 3
    • 4

    二 .初始化完成后执行run方法

    再来看一下初始化后的SpringApplication对象调用的run方法

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        //1.通过SpringFactoriesLoader查找并加载所有的SpringApplicationRunListeners,通过调用
        //starting()方法通知所有的SpringApplicationRunListeners:应用开始启动了
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            //2.创建并配置当前应用将要使用的Environment
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
            //3.打印banner
            Banner printedBanner = printBanner(environment);
            //4.根据是否是web项目,来创建不同的ApplicationContext容器
            context = createApplicationContext();
            //5.创建一系列FailureAnalyzer
            exceptionReporters = (
                    . class,
            new Class[]{. class },context);
            //6.初始化ApplicationContext
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            //7.调用ApplicationContext的refresh()方法,刷新容器
            refreshContext(context);
            //8.查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们。
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
            handleRunFailure(context, listeners, exceptionReporters, ex);
            throw new IllegalStateException(ex);
        }
        listeners.running(context);
        return context;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    1.通过SpringFactoriesLoader查找并加载所有的SpringApplicationRunListeners,通过调用starting()方法通知所有的SpringApplicationRunListeners:应用开始启动了。(SpringApplicationRunListeners其本质上就是一个事件发布者,它在SpringBoot应用启动的不同时间点发布不同应用事件类型(ApplicationEvent),如果有哪些事件监听者(ApplicationListener)对这些事件感兴趣,则可以接收并且处理) 看下SpringApplicationRunListeners源码:

    public interface SpringApplicationRunListener {
    
    // 运行run方法时立即调用此方法,可以用户非常早期的初始化工作
    void starting();
    
    // Environment准备好后,并且ApplicationContext创建之前调用
    void environmentPrepared( environment);
    
    // ApplicationContext创建好后立即调用
    void contextPrepared( context);
    
    // ApplicationContext加载完成,在refresh之前调用
    void contextLoaded( context);
    
    // 当run方法结束之前调用
    void finished( context, Throwable exception);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    SpringApplicationRunListener只有一个实现类:EventPublishingRunListener。只会获取到一个EventPublishingRunListener的实例,我们来看看EventPublishingRunListener的starting()方法的内容:

    public void starting() {
    // 发布一个ApplicationStartedEvent
    this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.application, this.args));
    }
    
    • 1
    • 2
    • 3
    • 4

    2.创建并配置当前应用将要使用的Environment,Environment用于描述应用程序当前的运行环境,其抽象了两个方面的内容:配置文件(profile)和属性(properties),不同的环境(eg:生产环境、预发布环境)可以使用不同的配置文件,而属性则可以从配置文件、环境变量、命令行参数等来源获取。因此,当Environment准备好后,在整个应用的任何时候,都可以从Environment中获取资源。

    判断Environment是否存在,不存在就创建(如果是web项目就创建StandardServletEnvironment,否则创建StandardEnvironment)
    
    配置Environment:配置profile以及properties
    
    调用SpringApplicationRunListener的environmentPrepared()方法,通知事件监听者:应用的Environment已经准备好
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.打印banner(可以自定义)

    4.根据是否是web项目,来创建不同的ApplicationContext容器

    5.创建一系列FailureAnalyzer,创建流程依然是通过SpringFactoriesLoader获取到所有实现FailureAnalyzer接口的class,然后在创建对应的实例。FailureAnalyzer用于分析故障并提供相关诊断信息。

    6.初始化ApplicationContext

    将准备好的Environment设置给ApplicationContext
    
    遍历调用所有的ApplicationContextInitializer的initialize()方法来对已经创建好的ApplicationContext进行进一步的处理
    
    调用SpringApplicationRunListener的contextPrepared()方法,通知所有的监听者:ApplicationContext已经准备完毕
    
    将所有的bean加载到容器中
    
    调用SpringApplicationRunListener的contextLoaded()方法,通知所有的监听者:ApplicationContext已经装载完毕
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    7.调用ApplicationContext的refresh()方法,刷新容器

    这里的刷新和spring中刷新原理类似,这里重点关注invokeBeanFactoryPostProcessors(beanFactory);方法,主要完成获取到所有的BeanFactoryPostProcessor来对容器做一些额外的操作,通过源可以进入到PostProcessorRegistrationDelegate类 的invokeBeanFactoryPostProcessors()方法,会获取类型为BeanDefinitionRegistryPostProcessor的beanorg.springframework.context.annotation.internalConfigurationAnnotationProcessor,对应的Class为ConfigurationClassPostProcessor。ConfigurationClassPostProcessor用于解析处理各种注解,
    
    包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。当处理@import注解的时候,就会调用<自动配置>这一小节中的EnableAutoConfigurationImportSelector.selectImports()来完成自动配置功能
    
    • 1
    • 2
    • 3

    8.查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们

  • 相关阅读:
    Vue_Bug NPM下载速度过慢
    Vue3项目上线打包优化
    鸿蒙HarmonyOS应用开发初体验
    中国人民大学加拿大女王金融硕士项目——只有不断提升自己,才能立于不败之地
    数字化转型重塑企业竞争优势,SaaS电商系统助力锂电池行业实现降本增效
    数学术语之源——纤维(fiber)
    模型部署 利用Tensorflow Serving部署模型
    【Qt QML】TabBar的用法
    Redis 持久化机制
    java.sql.SQLException: Cannot set createTime: incompatible types
  • 原文地址:https://blog.csdn.net/weixin_42232931/article/details/125465704