zoukankan      html  css  js  c++  java
  • SpringBoot启动原理(基于2.3.9.RELEASE版本)

    版本

    以下源码的 SpringBoot 版本:2.3.9.RELEASE。

    总体上

    分为两大步:

    • 启动类上注解@SpringBootApplication
    • 启动类中的main方法:org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String...)

    main方法上的注解:@SpringBootApplication

    源码

    三个注解核心注解:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
    

    @SpringBootConfiguration

    根据Javadoc可知,该注解作用就是将当前的类作为一个JavaConfig,然后触发注解@EnableAutoConfiguration@ComponentScan处理,本质上与@Configuration注解没有区别。

    @ComponentScan

    扫描的 Spring 对应的组件,如 @Componet@Repository

    我们可以通过 basePackages 等属性来细粒度的定制 @ComponentScan 自动扫描的范围,如果不指定,则默认Spring框架实现会从声明 @ComponentScan 所在类的package进行扫描,所以 SpringBoot启动类最好是放在根package下,我们自定义的类就放在对应的子package下,这样就可以不指定 basePackages

    @EnableAutoConfiguration

    @EnableAutoConfiguration总结

    • @AutoConfigurationPackage

      • 注册当前启动类的根 package
      • 注册 org.springframework.boot.autoconfigure.AutoConfigurationPackagesBeanDefinition
    • @Import(AutoConfigurationImportSelector.class)

      • 可以看到实现了 DeferredImportSelector 接口,该接口继承自 ImportSelector,根据 Javadoc 可知,多用于导入被 @Conditional 注解的Bean,之后会进行 filter 操作
      • AutoConfigurationImportSelector.AutoConfigurationGroup#process 方法,SpringBoot 启动时会调用该方法,进行自动装配的处理,见SpringApplication#run(java.lang.String...)源码解析
        • SpringApplication#run(java.lang.String...)
        • SpringApplication#refreshContext(即 Spring IOC 容器初始化的过程中
        • ConfigurationClassParser#parse
        • AutoConfigurationImportSelector.AutoConfigurationGroup#process
      • 通过SpringFactoriesLoader#loadFactoryNames获取应考虑的自动配置名称,例如来源于 spring-boot-autoconfigure jar包下的 META-INF/spring.factories 文件下的配置
      • 通过 filter 过滤掉当前环境不需要自动装配的类,各种 @Conditional 不满足就被过滤掉
      • 需要自动装配的全路径类名注册到 SpringIOC 容器,自此 SpringBoot 自动装配完成!

    源码

    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    

    SpringBoot 自动装配的核心注解,在 Spring 框架中就提供了各种以@Enable开头的注解,例如: @EnableCircuitBreaker@EnableScheduling等;

    @EnableAutoConfiguration借助@Import的支持,收集和注册特定场景相关的bean定义;

    自动装配的类,通常是 @Configuration 类,通过 SpringFactoriesLoader 加载到 Spring 容器。

    @AutoConfigurationPackage

    注册当前启动类的根package

    注册 org.springframework.boot.autoconfigure.AutoConfigurationPackagesBeanDefinition

    AutoConfigurationImportSelector

    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
      ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    
    • 可以看到实现了 DeferredImportSelector 接口,该接口继承自ImportSelector,根据Javadoc可知,多用于导入被@Conditional注解的Bean

    • DeferredImportSelector接口中有个process方法,SpringBoot启动时会调用该方法,进行自动装配的处理,大体流程如下:

      • SpringApplication#run(java.lang.String...)
      • SpringApplication#refreshContext
      • ConfigurationClassParser#parse
      • AutoConfigurationImportSelector.AutoConfigurationGroup#process
    • AutoConfigurationImportSelector.AutoConfigurationGroup#process方法的源码:

      调用了AutoConfigurationImportSelector#getAutoConfigurationEntry方法,获取需要自动装配类

      public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
       Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
         () -> String.format("Only %s implementations are supported, got %s",
           AutoConfigurationImportSelector.class.getSimpleName(),
           deferredImportSelector.getClass().getName()));
          // 获取需要自动装配类
       AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
         .getAutoConfigurationEntry(annotationMetadata);
       this.autoConfigurationEntries.add(autoConfigurationEntry);
       for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        this.entries.putIfAbsent(importClassName, annotationMetadata);
       }
      }
      
    • AutoConfigurationImportSelector#getAutoConfigurationEntry大体流程如下:

      • 通过SpringFactoriesLoader#loadFactoryNames获取应考虑的自动配置名称,例如来源于 spring-boot-autoconfigure jar包下的META-INF/spring.factories文件下的配置

      • 通过filter过滤掉当前环境不需要自动装配的类,比如没有集成RabbitMQ,就不需要,或者有的条件@Conditional不满足也不需要自动装配

      • 返回需要自动装配的全路径类名

      • 源码如下:

      protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
       if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
       }
       AnnotationAttributes attributes = getAttributes(annotationMetadata);
          // 获取预先定义的应考虑的自动配置类名称
       List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
       configurations = removeDuplicates(configurations);
       Set<String> exclusions = getExclusions(annotationMetadata, attributes);
       checkExcludedClasses(configurations, exclusions);
       configurations.removeAll(exclusions);
          // 通过filter过滤掉当前环境不需要自动装配的类,比如没有集成RabbitMQ,就不需要,或者有的条件@Conditional不满足也不需要自动装配
       configurations = getConfigurationClassFilter().filter(configurations);
       fireAutoConfigurationImportEvents(configurations, exclusions);
          // 返回需要自动装配的全路径类名
       return new AutoConfigurationEntry(configurations, exclusions);
      }
      
    • AutoConfigurationImportSelector#getCandidateConfigurations源码如下:

      protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
       List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
       Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
         + "are using a custom packaging, make sure that file is correct.");
       return configurations;
      }
      
    • 通过SpringFactoriesLoader#loadFactoryNames获取应考虑的自动配置名称,通过META-INF/spring.factories下的配置,例如:

      image-20210418175725765

      spring-boot-autoconfigure jar包下的 spring.factories 文件:

    • 执行完configurations = getConfigurationClassFilter().filter(configurations);之后,各种@Conditional不满足就被过滤掉,剩下35个了

    • 可以通过如下方法进行验证,结果没有 RabbitAutoConfiguration 相关的Bean,抛出异常 NoSuchBeanDefinitionException

      @SpringBootApplication
      public class SpringDemosApplication implements ApplicationContextAware {
        private static ApplicationContext applicationContext;
      
        public static void main(String[] args) {
          SpringApplication.run(SpringDemosApplication.class, args);
          System.out.println(applicationContext.getBean(AopAutoConfiguration.class));
          System.out.println(applicationContext.getBean(RabbitAutoConfiguration.class));
        }
      
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
          SpringDemosApplication.applicationContext = applicationContext;
        }
      }
      

    main方法

    例子

    main方法里调用org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String...)方法

    @SpringBootApplication
    public class SpringDemosApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringDemosApplication.class, args);
        }
    }
    

    SpringApplication#run

    调用另外一个同名的重载方法run

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
     return run(new Class<?>[] { primarySource }, args);
    }
    

    实例化SpringApplication对象

    1. 首先会实例化SpringApplication一个对象
    2. 构造方法里初始化一些属性,比如webApplicationType,比如"SERVLET",初始化一些listeners
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
     return new SpringApplication(primarySources).run(args);
    }
    
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
     this.resourceLoader = resourceLoader;
     Assert.notNull(primarySources, "PrimarySources must not be null");
     this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 初始化webApplicationType,比如"SERVLET"
     this.webApplicationType = WebApplicationType.deduceFromClasspath();
     setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 初始化一些listeners
     setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
     this.mainApplicationClass = deduceMainApplicationClass();
    }
    

    SpringApplication#run(java.lang.String...)源码解析

    经典的观察者模式,只要你把事件广播的顺序理解了,那整个流程就很容易串起来了:

    1. 创建一个StopWatch实例,用来记录SpringBoot的启动时间
    2. 通过SpringFactoriesLoader加载listeners:比如EventPublishingRunListener
    3. 发布SprintBoot开始启动事件(EventPublishingRunListener#starting()
    4. 创建和配置environment(environmentPrepared()
    5. 打印SpringBoot的banner和版本
    6. 创建对应的ApplicationContext:Web类型,Reactive类型,普通的类型(非Web)
    7. prepareContext
      1. 准备ApplicationContext,Initializers设置到ApplicationContext(contextPrepared())
      2. 打印启动日志,打印profile信息(如dev, test, prod)
      3. 最终会调用到AbstractApplicationContext#refresh方法,实际上就是Spring IOC容器的创建过程,并且会进行自动装配的操作,以及发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成(contextLoaded())
    8. afterRefresh hook方法
    9. stopWatch停止计时,日志打印总共启动的时间
    10. 发布SpringBoot程序已启动事件(started())
    11. 调用ApplicationRunner和CommandLineRunner
    12. 最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了(running())
    public ConfigurableApplicationContext run(String... args) {
        // 创建一个StopWatch实例,用来记录SpringBoot的启动时间
     StopWatch stopWatch = new StopWatch();
     stopWatch.start();
     ConfigurableApplicationContext context = null;
     Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
     configureHeadlessProperty();
        // 通过SpringFactoriesLoader加载listeners:比如EventPublishingRunListener
     SpringApplicationRunListeners listeners = getRunListeners(args);
        // 发布SprintBoot启动事件:ApplicationStartingEvent
     listeners.starting();
     try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
         // 创建和配置environment,发布事件:SpringApplicationRunListeners#environmentPrepared
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
         // 打印SpringBoot的banner和版本
      Banner printedBanner = printBanner(environment);
         // 创建对应的ApplicationContext:Web类型,Reactive类型,普通的类型(非Web)
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
        new Class[] { ConfigurableApplicationContext.class }, context);
         // 准备ApplicationContext,Initializers设置到ApplicationContext后发布事件:ApplicationContextInitializedEvent
         // 打印启动日志,打印profile信息(如dev, test, prod)
         // 调用EventPublishingRunListener发布ApplicationContext加载完毕事件:ApplicationPreparedEvent
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
         // 最终会调用到AbstractApplicationContext#refresh方法,实际上就是Spring IOC容器的创建过程,并且会进行自动装配的操作
         // 以及发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成
      refreshContext(context);
         // hook方法
      afterRefresh(context, applicationArguments);
         // stopWatch停止计时,日志打印总共启动的时间
      stopWatch.stop();
      if (this.logStartupInfo) {
       new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
         // 发布SpringBoot程序已启动事件ApplicationStartedEvent
      listeners.started(context);
         // 调用ApplicationRunner和CommandLineRunner
      callRunners(context, applicationArguments);
     }
     catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
     }
    
     try {
         // 最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了
      listeners.running(context);
     }
     catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
     }
     return context;
    }
    

    SpringBoot启动事件

    • SpringApplicationRunListeners的唯一实现是EventPublishingRunListener;

    • 整个SpringBoot的启动,流程就是各种事件的发布,调用EventPublishingRunListener中的方法。

    • 只要明白了EventPublishingRunListener中事件发布的流程,也就明白了SpringBoot启动的大体流程

    EventPublishingRunListener

    方法说明如下:

    public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    
     private final SpringApplication application;
    
     private final String[] args;
    
     private final SimpleApplicationEventMulticaster initialMulticaster;
    
     public EventPublishingRunListener(SpringApplication application, String[] args) {
      this.application = application;
      this.args = args;
      this.initialMulticaster = new SimpleApplicationEventMulticaster();
      for (ApplicationListener<?> listener : application.getListeners()) {
       this.initialMulticaster.addApplicationListener(listener);
      }
     }
    
     @Override
     public int getOrder() {
      return 0;
     }
    
        // SpringBoot启动事件
     @Override
     public void starting() {
      this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
     }
    
        // 创建和配置环境
     @Override
     public void environmentPrepared(ConfigurableEnvironment environment) {
      this.initialMulticaster
        .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
     }
    
        // 准备ApplicationContext
     @Override
     public void contextPrepared(ConfigurableApplicationContext context) {
      this.initialMulticaster
        .multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
     }
    
        // 发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成
     @Override
     public void contextLoaded(ConfigurableApplicationContext context) {
      for (ApplicationListener<?> listener : this.application.getListeners()) {
       if (listener instanceof ApplicationContextAware) {
        ((ApplicationContextAware) listener).setApplicationContext(context);
       }
       context.addApplicationListener(listener);
      }
      this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
     }
    
        // SpringBoot已启动事件
     @Override
     public void started(ConfigurableApplicationContext context) {
      context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
      AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
     }
    
        // "SpringBoot现在可以处理接受的请求"事件
     @Override
     public void running(ConfigurableApplicationContext context) {
      context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
      AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
     }
    
     @Override
     public void failed(ConfigurableApplicationContext context, Throwable exception) {
      ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
      if (context != null && context.isActive()) {
       // Listeners have been registered to the application context so we should
       // use it at this point if we can
       context.publishEvent(event);
      }
      else {
       // An inactive context may not have a multicaster so we use our multicaster to
       // call all of the context's listeners instead
       if (context instanceof AbstractApplicationContext) {
        for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
          .getApplicationListeners()) {
         this.initialMulticaster.addApplicationListener(listener);
        }
       }
       this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
       this.initialMulticaster.multicastEvent(event);
      }
     }
    
     private static class LoggingErrorHandler implements ErrorHandler {
    
      private static final Log logger = LogFactory.getLog(EventPublishingRunListener.class);
    
      @Override
      public void handleError(Throwable throwable) {
       logger.warn("Error calling ApplicationEventListener", throwable);
      }
    
     }
    
    }
    

    SpringIOC 容器初始化过程

    由于现在大都是用SpringBoot开发,所以呢,Spring IOC 初始化的源码,就是AnnotationConfigApplicationContext中的源码,IOC的初始化就是该类实例创建的过程。

    创建的过程(AnnotationConfigApplicationContext的构造方法),由于debug过这个源码我个人把它分为两大步(暂时我先写出我的总结,后续看是否有时间能写一篇关于debug的过程):

    1. 给我们的Bean,创建与之对应的BeanDefinition,然后把他们放入ConcurrentHashMap(key:beanName和value:beanDefinition)中;BeanDefinition实际上包括一些Bean的信息,比如BeanName, Scope, 是否被@Primary注解修饰,是否是@Lazy,以及@Description等注解
    2. refresh()方法: 创建IOC需要的资源
    • 初始化BeanFactory, set一些属性,如BeanClassLoadersystemEnvironment
    • 如果是SpringBoot程序,会调用方法进行自动装配:AutoConfigurationImportSelector.AutoConfigurationGroup#process,见:@EnableAutoConfiguration的总结
    • 注册MessageSource,国际化相关的资源,到ApplicationContext
    • 注册ApplicationListener到ApplicationContext
    • 实例化化lazy-init的Bean
    • 最后,publish相关的事件,ApplicationContext 就初始化完成,整个IOC容器初始化完成(IOC容器的本质就是初始化BeanFactory和ApplicationContext),就可以从IOC容器中获取Bean自动注入了
  • 相关阅读:
    常用正则表达式
    C# 计算时间差 用timespan函数
    SQL Server日期时间格式转换字符串详解
    .net数据根据字段进行分类(linq语句)
    IIS设置允许下载.exe文件解决方法
    IIS 添加mime 支持 apk,exe,.woff,IIS MIME设置 ,Android apk下载的MIME 设置 苹果ISO .ipa下载mime 设置
    高版本jQuery设置checkbox状态注意事项
    sql大小转换函数
    常用的正则验证
    position布局影响点击事件以及冒泡获取事件目标
  • 原文地址:https://www.cnblogs.com/theRhyme/p/how-does-springboot-start.html
Copyright © 2011-2022 走看看