揭秘SpringBoot(一)_SpringBoot运行原理

引:可能你早已开始使用SpringBoot,但是你却不知道SpringBoot是个什么东西,他又是怎么运行的,这里给你答案!

SpringBoot是什么

用过Spring的人都知道,你肯定需要些很多很多的XML来用配置复杂的依赖关系,你肯定厌倦了。这个时候SpringBoot出现了,
SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置(想想肯定会很开心)。

核心理念

习惯优于配置

SpringBoot与Spring Framework的区别

做个下面的比喻

SpringFramework 就像一个大型电子器件生产公司,它生产的电子器件(比如zookeeper,redis等整合包)都很优秀,当其他小公司(开发者)生产机器人(项目)需要使用到它的电子器件时,就发现需要大量的焊接工作(配置XML)来连接自己的电子器件(zookeeper,redis),这样真的太耗时了,而SpringBoot 就像这个电子器件生产公司在原来电子器件的基础上包装出来的许多统一的插口(各种starter),这些插头可以与小公司的电子器件可以直接连接,不需要焊接工作就可以直接使用。

通过上面的比喻我们可以了解到他们两者的区别,也发现其实SpringBoot并不是什么新东西,它只是原来Spring的基础上重新包装过,从而简化了Spring的相关配置。

SpringBoot的核心功能

  1. SpringBoot可以以jar包的形式独立运行,因为SpringBoot内嵌Servlet容器,如Tomcat、Jetty。
  2. Spring会根据类路径中的jar包、类,为jar包里的类自动配置Bean,极大减少了我们要使用的配置。
  3. 提供starter(起动机)简化了Maven配置(依赖加载),一个starter依赖抵了好几个依赖。

SpringBoot运行原理

  1. 对于SpringBoot工程我们总是先看到它的启动类,如下:

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    public class SpringbootLoginApplication {

    public static void main(String[] args) {
    SpringApplication.run(SpringbootLoginApplication.class, args);
    }
    }
  2. 对于SpringBoot的运作原理,我们应该先从启动类的@SpringBootApplication注解来分析,这个注解是一个组合注解,我们进入它的源码看看:

    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
    @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 {
    @AliasFor(
    annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
    annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
    annotation = ComponentScan.class,
    attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
    annotation = ComponentScan.class,
    attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
    }

    总得来说最重要的就是@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan这三个注解,我们一个个分析:

    1. @SpringBootConfiguration

      我们进入该注解,我发现它的代码如下:

      1
      2
      3
      4
      5
      6
      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Configuration
      public @interface SpringBootConfiguration {
      }

      你会发现其核心注解就是@Configuration,它对于我们来说应该不陌生,因为它就是Java配置形式的Spring Ioc容器的配置类使用的那个@Configuration(相当于XML配置文件的一个Bean),SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。

    2. @ComponentScan

      这个注解很简单,很常见,但是也很重要。它主要的作用就是自动扫描并加载符合条件的组件(比如@Controller和@Service等)或者bean定义,最终将这些bean定义加载到IoC容器中。我们看看它的源码:

      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
      @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.TYPE})
      @Documented
      @Repeatable(ComponentScans.class)
      public @interface ComponentScan {
      // 设置默认路径
      @AliasFor("basePackages")
      String[] value() default {};
      // 如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。
      // 所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。
      @AliasFor("value")
      String[] basePackages() default {};
      Class<?>[] basePackageClasses() default {};
      Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
      Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
      ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
      // 扫描类型
      String resourcePattern() default "**/*.class";
      // 可设置过滤器
      boolean useDefaultFilters() default true;
      ComponentScan.Filter[] includeFilters() default {};
      ComponentScan.Filter[] excludeFilters() default {};
      // 配置懒加载,如果没被使用,就先不生成Bean
      boolean lazyInit() default false;

      @Retention(RetentionPolicy.RUNTIME)
      @Target({})
      public @interface Filter {
      FilterType type() default FilterType.ANNOTATION;

      @AliasFor("classes")
      Class<?>[] value() default {};

      @AliasFor("value")
      Class<?>[] classes() default {};

      String[] pattern() default {};
      }
      }
    3. @EnableAutoConfiguration

      这个注解可以说是SpringBoot自动配置的核心了,灰常重要。我们进去看一下它的源码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Inherited
      @AutoConfigurationPackage
      // 借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器
      @Import({AutoConfigurationImportSelector.class})
      public @interface EnableAutoConfiguration {
      String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

      Class<?>[] exclude() default {};

      String[] excludeName() default {};
      }

      我们需要好好看看AutoConfigurationImportSelector,它利用SpringFactoriesLoader.loadFactoryNames方法来扫描具有META-INF/spring.factories 文件的jar包,代码如下:

      1
      2
      3
      4
      protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
      List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
      return configurations;
      }

      配合@EnableAutoConfiguration使用的话,它更多是提供一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名如org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组@Configuration类,并将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的配置项实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个(相当于一个XML文件)并加载到IoC容器(和Spring一样)。

      我们可以看看spring-boot-autoconfigure.jar 里就有一个spring.factories 文件,此文件中声明了有哪些自动配置。我们看一点点:

      1
      2
      3
      4
      5
      # Auto Configure
      org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
      # 配置AOP对象
      org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
  3. 看完@SpringBootApplication注解,我们再看看SpringApplication的run方法。

    1. 我们通过debug发现将进入这个方法:

      1
      2
      3
      4
      public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
      // 会先创建SpringApplication对象实例,然后调用它的实例run方法
      return (new SpringApplication(primarySources)).run(args);
      }
    2. 我们看看在实例初始化的过程中他做了什么:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
      this.sources = new LinkedHashSet();
      // banner的打印模式,此时是控制台模式
      this.bannerMode = Mode.CONSOLE;
      // 开启日志
      this.logStartupInfo = true;
      // 启用CommandLineProperties
      this.addCommandLineProperties = true;
      // 开启headless模式支持,Headless模式是在缺少显示屏、键盘或者鼠标时的系统配置(自行了解)
      this.headless = true
      // 启用注册ShutdownHook,用于在非Web应用中关闭IoC容器和资源
      this.registerShutdownHook = true;
      this.additionalProfiles = new HashSet();
      this.resourceLoader = resourceLoader;
      this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
      // 判断是否是web运行环境(Servlet)
      this.webApplicationType = this.deduceWebApplicationType();
      // 设置初始化器(在META-INF/spring.factories 文件里,可扩展)
      this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
      // 设置监听器(在META-INF/spring.factories 文件里,可扩展)
      this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
      // 推断并设置main方法的定义类
      this.mainApplicationClass = this.deduceMainApplicationClass();
      }
    3. SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑,如下:

      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
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      public ConfigurableApplicationContext run(String... args) {
      // 开启任务执行时间监听器
      StopWatch stopWatch = new StopWatch();
      stopWatch.start();
      ConfigurableApplicationContext context = null;
      Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
      // 设置系统属性
      this.configureHeadlessProperty();
      //开启广播,宣告SpringBoot要开始执行了
      SpringApplicationRunListeners listeners = this.getRunListeners(args);
      listeners.starting();

      Collection exceptionReporters;
      try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 创建并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)。
      ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
      // 宣告SpringBoot应用使用的Environment准备好了。
      this.configureIgnoreBeanInfo(environment);
      // 决定是否打印Banner
      Banner printedBanner = this.printBanner(environment);
      // 根据用户是否明确设置了applicationContextClass类型,决定该为当前SpringBoot应用创建什么类型的ApplicationContext
      // 然后根据条件决定是否添加ShutdownHook,决定是否使用自定义的BeanNameGenerator,决定是否使用自定义的ResourceLoader,
      // 最重要的是将之前准备好的Environment设置给创建好的ApplicationContext使用。
      context = this.createApplicationContext();
      // 得到异常报告者
      exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, new Object[]{context});
      // ApplicationContext创建好之后,遍历调用ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理。
      // 遍历调用所有SpringApplicationRunListener的contextPrepared()方法。
      // 将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext
      // (很重要,可以进入看看)
      this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      // 初始化所有自动配置类,调用ApplicationContext的refresh()方法
      this.refreshContext(context);
      // 调用所有的SpringApplicationRunListener的finished()方法,宣告SpringBoot已经完成了ApplicationContext初始化的全部过程。
      this.afterRefresh(context, applicationArguments);
      //关闭任务执行时间监听器
      stopWatch.stop();
      if(this.logStartupInfo) {
      // //如果开启日志,则打印执行的时间
      (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
      }

      listeners.started(context);
      this.callRunners(context, applicationArguments);
      } catch (Throwable var10) {
      // //调用异常分析器打印报告,调用所有的SpringApplicationRunListener的finished()方法将异常信息发布出去
      this.handleRunFailure(context, var10, exceptionReporters, listeners);
      throw new IllegalStateException(var10);
      }

      try {
      listeners.running(context);
      return context;
      } catch (Throwable var9) {
      this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
      throw new IllegalStateException(var9);
      }
      }

参考

  1. Spring Boot干货系列:(三)启动原理解析
  2. Spring Boot学习笔记03–深入了解SpringBoot的启动过程
  3. spring boot应用启动原理分析