2025/03/17

SpringBoot3 - Spring Boot 啟動過程

啟動入口

@SpringBootApplication
public class Demo1Application {

    public static void main(String[] args) {
        SpringApplication.run(Demo1Application.class);
    }

}

@SpringBootApplication 裡面就包含了

  • @SpringBootConfiguration

  • @EnableAutoConfiguration

    啟用應用的自動設定功能

  • @ComponentScan

    自動掃描 sub packages 裡面的 Spring Component


啟動 application

SpringApplication.run(Demo1Application.class);

也可以拆開

SpringApplication springApplication = new SpringApplication(Demo1Application.class);
springApplication.run(args);

也可以用 SpringApplicationBuilder

new SpringApplicationBuilder()
    .sources(Parent.class)
    .child(Demo1Application.class)
    .banerMode(Banner.Mode.OFF)
    .run(args);

jar 啟動

application 的 main 通常是在 console 啟動時被呼叫,如果打包成 jar,就會是另一種狀況

project 可用 mvn package 打包成 jar

|____org
| |____springframework
| | |____boot
| | | |____loader
| | | | |____ref
| | | | |____net
| | | | |____jarmode
| | | | |____launch
| | | | |____jar
| | | | |____zip
| | | | |____nio
| | | | |____log
|____META-INF
| |____MANIFEST.MF
| |____maven
| | |____tw.com.test
| | | |____demo1
| | | | |____pom.xml
| | | | |____pom.properties
| |____services
| | |____java.nio.file.spi.FileSystemProvider
|____BOOT-INF
| |____classes
| | |____tw
| | | |____com
| | | | |____test
| | | | | |____demo1
| | | | | | |____Demo1Application.class
| | |____application.yml
| |____layers.idx
| |____classpath.idx
| |____lib
  • BOOT-INF: 啟動 application 所需要的 classes

  • META-INF: application打包的相關描述檔案

  • org: spring boot 啟動所需要的引導類別


META-INF/MENIFEST.MF

Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.4.2
Build-Jdk-Spec: 17
Implementation-Title: demo1
Implementation-Version: 0.0.1-SNAPSHOT
Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: tw.com.test.demo1.Demo1Application
Spring-Boot-Version: 3.3.5
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx

Main-Class: 啟動的引導類別

Start-Class: application 啟動類別


啟動的 log 可透過設定關閉

spring:
  main:
    log-startup-info: false

或是

SpringApplication springApplication = new SpringApplication(Demo1Application.class);
springApplication.setLogStartupInfo(false);
springApplication.run(args);

啟動失敗分析

由 FailureAnalyzer interface 處理

ex: PortInUseFailureAnalyzer 可處理重複使用 TCP Port 的 exception

可以自訂一個 PortInUseFailureAnalyzer

package tw.com.test.demo1;

import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.web.server.PortInUseException;

public class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer<PortInUseException> {
    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {
        return new FailureAnalysis(cause.getPort()+"PortInUse", "check "+cause.getPort(), cause);
    }
}

然後在 META-INF/spring.factories

org.springframework.boot.diagnostics.FailureAnalyzer=\
  tw.com.test.demo1.PortInUseFailureAnalyzer

也可以自訂自己的 applicaton exception,以及對應的 FailureAnalyzer

啟動 Banner

Text to ASCII Art Generator (TAAG) 這是將 text 轉換為 ascii 文字的網頁工具。

將要列印的 application 文字,轉換為 ascii 後,儲存到 /resources/banner.txt。啟動 application 時,就會看到 banner 已經換掉了。

以下是一些相關設定

# banner.txt encoding
spring.banner.charset=UTF-8
# banner.txt filepath
spring.banner.location=classpath:banner.txt
# console/log/off
spring.main.banner-mode=console

啟動事件與 listener

spring-boot-3.3.5.jar

META-INF/spring.factories 裡面註冊的 listener,這些都實作了 Spring 的 ApplicationListener interface

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener

啟動事件的順序

  1. ApplicationStartingEvent

    spring application 啟動前發送

  2. ApplicationEnvironmentPreparedEvent

    已知要在 context 使用 spring 的 Environment 時,在 context 建立之前發送

  3. ApplicationContextInitializedEvent

    ApplicationContext 準備好了,ApplicationContextInitializer 已被呼叫,在 bean 載入前發送

  4. ApplicationPreparedEvent

    在 context refresh 前,且在 bean 定義被載入後發送

  5. ApplicationStartedEvent

    在 Spring context refresh 以後,在 application / CLI runner 呼叫前發送

  6. AvailabilityChangeEvent

    跟著上一個事件後發送,ReadinessState.CORRECT,代表 application 已處於活動狀態

  7. ApplicationReadyEvent

    在 application / CLI runner 呼叫後發送

  8. AvailabilityChangeEvent

    跟著上一個事件後發送,ReadinessState.ACCEPTING_TRAFFIC,代表 application 已經準備接收 request

  9. ApplicationFailedEvent

    在啟動異常時發送

以上只有 SpringApplication 發出的 SpringApplicationEvents 事件。

以下事件會在 ApplicationPreparedEvent 後,ApplicationStartedEvent 前發送

  1. WebServerInitializedEvent

    WebServer 啟動後發送。包含 ServletWebServerInitialziedEvent, ReactiveWebServerInitializedEvent

  2. ContextRefreshedEvent

    在 ApplicationContext refresh 後發送


自訂事件 listener

Demo1ApplicationListener.java

package tw.com.test.demo1;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class Demo1ApplicationListener implements ApplicationListener<AvailabilityChangeEvent> {
    @Override
    public void onApplicationEvent(AvailabilityChangeEvent event) {
        log.info("got event: {}, state: {}", event, event.getState());
        if( ReadinessState.ACCEPTING_TRAFFIC == event.getState() ) {
            log.info("applcation is started!");
        }
    }
}

啟動測試

2024-11-15T16:26:43.269+08:00  INFO 4595 --- [           main] t.c.test.demo1.Demo1ApplicationListener  : got event: org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@5674e1f2, started on Fri Nov 15 16:26:40 CST 2024], state: CORRECT
2024-11-15T16:26:43.273+08:00  INFO 4595 --- [           main] t.c.test.demo1.Demo1ApplicationListener  : got event: org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@5674e1f2, started on Fri Nov 15 16:26:40 CST 2024], state: ACCEPTING_TRAFFIC
2024-11-15T16:26:43.273+08:00  INFO 4595 --- [           main] t.c.test.demo1.Demo1ApplicationListener  : applcation is started!

Runner

在 application 啟動前,執行一些 code

  • ApplicationRunner

  • CommandLineRunner

@Component 或是 lambda 的 @Bean兩種寫法

package tw.com.test.demo1;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@SpringBootApplication
@Slf4j
public class Demo1Application {

    public static void main(String[] args) {
        SpringApplication.run(Demo1Application.class);
    }

    @Component
    public class Demo1ApplicationRunner implements ApplicationRunner {
        @Override
        public void run(ApplicationArguments args) throws Exception {
            log.info("ApplicationRunner runner args={}", args);
        }
    }
//    @Bean
//    public ApplicationRunner applicationRunner() {
//        return new Demo1ApplicationRunner();
//    }

    @Bean
    public ApplicationRunner applicationRunner2() {
        return (args) -> {
            log.info("CommandLineRunner2 args={}", args);
        };
    }

    @Bean
    public CommandLineRunner commandLineRunner() {
        return (args) -> {
            log.info("CommandLineRunner args={}", args);
        };
    }
}

執行結果

2024-11-15T16:57:46.247+08:00  INFO 4793 --- [           main] tw.com.test.demo1.Demo1Application       : CommandLineRunner args={}
2024-11-15T16:57:46.248+08:00  INFO 4793 --- [           main] tw.com.test.demo1.Demo1Application       : CommandLineRunner2 args=org.springframework.boot.DefaultApplicationArguments@d535a3d
2024-11-15T16:57:46.249+08:00  INFO 4793 --- [           main] tw.com.test.demo1.Demo1Application       : ApplicationRunner runner args=org.springframework.boot.DefaultApplicationArguments@d535a3d

沒有留言:

張貼留言