Spring Boot 的启动

新的 Spring Boot 的文章

SpringBoot

  • 新建一个SpringBoot工程的示例

  • 关于启动流程

    • SpringBoot首先会创建SpringBootApplication对象

      • 初始化对象

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        //primarySources就是当前SpringBootApplication对象
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //判断是否是web项目
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer,然后保存
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //从类路径下找到ETA-INF/spring.factories配置的所有ApplicationListener
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
        }

        spring.factories4

        listener和initializer

    • 然后执行run方法
      Spring Boot应用的整个启动流程都封装在SpringApplication.run方法中,本质上其实就是在spring的基础之上做了封装,做了大量的扩张

      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
      60
      61
      62
      public ConfigurableApplicationContext run(String... args) {
      //启动开始停止的监听
      StopWatch stopWatch = new StopWatch();
      stopWatch.start();
      //声明IOC容器
      ConfigurableApplicationContext context = null;
      Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
      //是关于awt的
      configureHeadlessProperty();
      //通过SpringFactoriesLoader查找并加载所有的SpringApplicationRunListeners(从META-INF/spring.factories),通过调用starting()方法通知所有的SpringApplicationRunListeners:应用开始启动了
      SpringApplicationRunListeners listeners = getRunListeners(args);
      //回调所有的获取SpringApplicationRunListener.starting()方法
      listeners.starting();
      try {
      //封装命令行参数
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      //准备环境
      //创建环境完成后回调SpringApplicationRunListener.environmentPrepared(),表示环境准备完成
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
      //打印SPRING那个大图标
      Banner printedBanner = printBanner(environment);
      //创建IOC容器,会判断是不是web,利用反射创建容器
      context = createApplicationContext();
      //创建一系列FailureAnalyzer
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
      new Class[] { ConfigurableApplicationContext.class }, context);
      //准备上下文环境
      //将environment保存到IOC
      //还有applyInitializers(),回调之前保存的所有的ApplicationContextInitialize方法
      //还要回调所有的SpringApplicationRunListener的contextPrepared()
      //还有什么banner什么的
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      //prepareContext完成后回调所有的SpringApplicationRunListener的contextLoaded()
      //刷新容器,IOC容器初始化的过程,扫描、创建、加载所有的组件
      //如果是web,tomcat也在这里创建好
      refreshContext(context);
      //afterRefresh:从IOC容器中获取所有的ApplicationRunner(先)和CommandLineRunner(后)进行回调
      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, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
      }

      try {
      listeners.running(context);
      }
      catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
      }
      //返回启动的IOC容器
      return context;
      }
      1
      2
      3
      4
      private void configureHeadlessProperty() {
      System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
      System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
      }

      configureHeadlessProperty是关于awt的,给属性设值System.setProperty(),它的值来源于System.getProperty().为什么把属性从一个地方取出来,然后又设置到同一个地方?System中getProperty()有2个重载方法,但却只有一个setProperty()方法,其中getProperty()有单参和双参两方法,单参就是简单的获取属性,有就有,没有就没有,双参则聪明一点,在没有的时候会返回一个调用者指定的默认值,所以经过这样操作后,不管有没有那个属性,最终都能保证有。所以先取后设
      即使没有检测到显示器,也允许其启动。对于服务器来说,是不需要显示器的,所以要这样设置

    • 几个重要的事件回调机制

      • 在META-INF/spring.factories
        • ApplicationContextInitializer
        • SpringApplicationRunListener
      • IOC容器中
        • ApplicationRunner
        • CommandLineRunner
    • 实现它们来观察SpringBoot启动顺序

      • 四个实现
        4个实现

        1
        2
        3
        4
        5
        6
        7
        public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        //ConfigurableApplicationContext ioc
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("MyApplicationContextInitializer initialize: " + applicationContext);
        }
        }
    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
        
    ```java
    public class MySpringApplicationRunListener implements SpringApplicationRunListener {
    public MySpringApplicationRunListener(SpringApplication application, String[] args) {
    }

    @Override
    public void starting() {
    System.out.println("MySpringApplicationRunListener start");
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
    Object o = environment.getSystemProperties().get("os.name");
    System.out.println("SpringApplicationRunListener的environmentPrepared... os name:" + o);
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
    // context.getBean("xxx")
    System.out.println("SpringApplicationRunListener的contextPrepared (ioc)准备好");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {

    System.out.println("SpringApplicationRunListener的contextLoaded context加载ok");
    }

    @Override
    public void started(ConfigurableApplicationContext context) {

    System.out.println("MySpringApplicationRunListener started");
    }

    @Override
    public void running(ConfigurableApplicationContext context) {

    System.out.println("MySpringApplicationRunListener running");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {

    System.out.println("MySpringApplicationRunListener failed");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    @Component
    public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
    //args命令行参数
    System.out.println("MyApplicationRunner run");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    @Component
    public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
    System.out.println("MyCommandLineRunner run");
    }
    }
    记得ApplicationContextInitializer和SpringApplicationRunListener要在spring.factories声明

    spring.factories

    还有其他两个要@Component,它们两个B在IOC中
      
    
    1
    2
    3
    4
    5
        # Application Context Initializers
    org.springframework.context.ApplicationContextInitializer=\
    cn.kebabshell.springbootlearn.listener.MyApplicationContextInitializer
    org.springframework.boot.SpringApplicationRunListener=\
    cn.kebabshell.springbootlearn.listener.MySpringApplicationRunListener
    结果一目了然!! ![结果](F:\blog\source\_posts\SpringBoot的小复习\test3.png)
    • 自定义starter场景启动器
      官方的有例如aop什么的

      • 分为starter和autoconfigurer
        例如
        test和autoconfig

      • 新建空工程,创建两个模块
        创建模块

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>

        <!--启动器-->
        <groupId>cn.kebabshell.starter</groupId>
        <artifactId>kebabshell-spring-boot-starter</artifactId>
        <version>1.0-SNAPSHOT</version>
        <dependencies>
        <!--引入自动配置模块-->
        <dependency>
        <groupId>cn.kebabshell</groupId>
        <artifactId>kebabshell-spring-boot-starter-autoconfigurer</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        </dependency>
        </dependencies>

        </project>
      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
          
      ```xml
      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.2.6.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
      </parent>
      <groupId>cn.kebabshell</groupId>
      <artifactId>kebabshell-spring-boot-starter-autoconfigurer</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <name>kebabshell-spring-boot-starter-autoconfigurer</name>
      <description>Demo project for Spring Boot</description>

      <properties>
      <java.version>1.8</java.version>
      </properties>

      <dependencies>
      <!--引入spring-boot-starter,所有starter的基本配置-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      </dependency>
      </dependencies>
      </project>
      1
          
    • autoconfigurer

      • 创建MyProperties

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
          @ConfigurationProperties(prefix = "kebabshell.test")
        //这里的prefix就是引入这个starter的项目在配置文件里面可以配置的前缀
        public class MyProperties {
        //自定义的一些属性
        private String prefix;
        private String suffix;

        public String getPrefix() {
        return prefix;
        }

        public void setPrefix(String prefix) {
        this.prefix = prefix;
        }

        public String getSuffix() {
        return suffix;
        }

        public void setSuffix(String suffix) {
        this.suffix = suffix;
        }
        }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
      
    - 创建TestService

    ```java
    public class TestService {
    private MyProperties properties;

    public MyProperties getProperties() {
    return properties;
    }

    public void setProperties(MyProperties properties) {
    this.properties = properties;
    }

    //根据别的项目调用test方法,可以进行一些操作
    public String test(String str){
    return "im config prefix:" + properties.getPrefix() + " suffix:" + properties.getSuffix() + " args:" + str;
    }
    }
    - 创建MyAutoConfiguration
      
      
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
      @Configuration
    @ConditionalOnWebApplication//Web才生效
    @EnableConfigurationProperties(MyProperties.class)
    public class MyAutoConfiguration {
    @Autowired
    private MyProperties properties;
    @Bean//加入ioc
    public TestService testService(){
    TestService testService = new TestService();
    testService.setProperties(properties);
    return testService;
    }
    }
    1
    2
    3
    4
    5
    6
    7

    - 创建spring.factories
    ![spring.factories](F:\blog\source\_posts\SpringBoot的小复习\test6.png)

    ```xml
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    cn.kebabshell.MyAutoConfiguration
    • 依次构建包
      install
      • 新建web工程

      • 引入自定义starter

        1
        2
        3
        4
        5
        6
        <!--自定义starter-->
        <dependency>
        <groupId>cn.kebabshell.starter</groupId>
        <artifactId>kebabshell-spring-boot-starter</artifactId>
        <version>1.0-SNAPSHOT</version>
        </dependency>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        
    - 创建controller就可以进行测试了

    ```java
    @RestController
    public class TestController {
    @Autowired
    private TestService service;
    @GetMapping("/test")
    public String test(){
    return service.test("hello");
    }
    }
    - 结果
    ![result](F:\blog\source\_posts\SpringBoot的小复习\test8.png)
    

参考1
参考2