Spring Boot(05)——SpringApplication介紹

SpringApplication介紹

通常啓動Spring Boot應用時調用SpringApplication類的static run()進行啓動。

@SpringBootApplication
public class Application {

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

}

其內部最終會轉換爲new一個SpringApplication對象,然後調用該對象的run方法,然後整個核心啓動邏輯就由SpringApplication對象的run方法完成。

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

調用SpringApplication的靜態run方法時,由於SpringApplication對象是在內部創建的,其會在啓動Spring Boot時使用一些默認的配置。如果我們需要進行一些自定義配置,則可以自己手動的new一個SpringApplication對象,進行一些特殊配置後再調用SpringApplication對象的實例run方法。比如Spring Boot默認在啓動的時候會輸出Spring Boot的banner,其中包含了Spring Boot的版本信息,如果我們不希望輸出該banner信息,則可以進行如下定製。

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.setBannerMode(Banner.Mode.OFF);
        app.run(args);
    }

}

ApplicationEvent及其監聽

SpringApplication在啓動Spring Boot的過程中會發布以下ApplicationEvent,也可以參考SpringApplication的實例run方法的實現。

  • ApplicationStartingEvent :會在進行其它操作之前發佈
  • ApplicationEnvironmentPreparedEvent : 接着是準備Environment,準備好了會發布該事件
  • ApplicationPreparedEvent :接着會構造ApplicationContext,在構造好ApplicationContext之後,調用其refresh()方法之前會發布該事件
  • ApplicationStartedEvent :在ApplicationContext進行refresh之後,調用ApplicationRunner和CommandLineRunner之前會發布該事件
  • ApplicationReadyEvent :在Spring Boot應用啓動完成之後,也就是在SpringApplication的run()調用馬上結束之前會發布該事件
  • ApplicationFailedEvent :在啓動過程中出現異常時會發布該事件

從上述的事件發佈過程可以看出,有些事件的發佈是在ApplicationContext還沒有準備好的情況下發布的,所以它們不能通過傳統的定義ApplicationEvent實現類爲bean容器中的一個bean的方式進行監聽。SpringApplication接口爲我們提供了專門的註冊這些監聽器的方法addListeners()。事件監聽器需要實現org.springframework.context.ApplicationListener接口。以下定義了兩個事件監聽器,都只是簡單的進行日誌輸出,然後在啓動應用的時候通過addListeners()添加了監聽器,程序啓動後會看到這兩個監聽器輸出的日誌信息。

@Slf4j
public class ApplicationStartingEventListener implements ApplicationListener<ApplicationStartingEvent> {

    @Override
    public void onApplicationEvent(ApplicationStartingEvent event) {
        log.info("收到Spring Boot應用準備啓動的事件[{}]", event);
    }

}

@Slf4j
public class ApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent> {

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        log.info("收到Spring Boot應用啓動完成的事件[{}]", event);
    }

}

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.addListeners(new ApplicationStartingEventListener(), new ApplicationReadyEventListener());
        app.run(args);
    }

}

特別需要注意的是在添加監聽器的時候不要調用SpringApplication的setListeners(),而要調用其addListeners()。因爲在構造SpringApplication對象的時候構造方法中已經通過Spring Boot的spring factory機制獲取並註冊了一些ApplicationListener(可以通過調用SpringApplication的getListeners()獲取到已經註冊的ApplicationListener),使用setListeners()會覆蓋掉已經註冊過的ApplicationListener。Spring Boot的spring factory機制是指可以創建一個META-INF/spring.factories文件,然後以接口類的全路徑名稱作爲Key,以實現類的全路徑名稱作爲Value,當有多個Value時以英文逗號分隔,當有多個Key時每個Key一行。它們會被SpringFactoriesLoader進行處理,可以通過它獲取到定義的接口對應的實現類。Spring Boot中有很多擴展都是基於這個機制進行的。上面的定義的ApplicationListener實現類,如果需要使用spring factory機制,則可以在spring.factories文件中添加如下內容:

org.springframework.context.ApplicationListener=com.elim.springboot.listener.ApplicationStartingEventListener,com.elim.springboot.listener.ApplicationReadyEventListener

當你覺得一行展示的內容太長了,期望折行展示時,可以在行末加上\,這語法跟定義properties文件是一樣的。實際上其內部也是按照properties文件進行解析的。

org.springframework.context.ApplicationListener=com.elim.springboot.listener.ApplicationStartingEventListener,\
com.elim.springboot.listener.ApplicationReadyEventListener

通過spring.factories文件定義了ApplicationListener後,我們的啓動應用代碼就可以改寫爲如下這種最簡單的方式了。

@SpringBootApplication
public class Application {

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

}

ApplicationContext的選擇

默認情況下,當ClassPath下存在SpringMVC相關的Class時將使用org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext,當不存在SpringMVC相關的Class,而是存在SpringWebFlux相關的Class時將使用org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext,當兩者都不存在時可以使用默認的org.springframework.context.annotation.AnnotationConfigApplicationContext。可以通過其setWebApplicationType(WebApplicationType webApplicationType)手動指定WebApplicationType,從而影響使用的ApplicationContext的選擇,也可以直接通過setApplicationContextClass(Class<? extends ConfigurableApplicationContext> applicationContextClass)指定需要使用的ApplicationContext對應的Class。

訪問命令行參數

調用SpringApplication的run()時傳遞的參數通常來自於命令行的參數,SpringApplication內部在調用run()時會把它們封裝爲一個ApplicationArguments對象,並且會把它定義爲bean容器中的一個bean。如果在應用中需要訪問命令行傳遞的參數,則可以通過注入ApplicationArguments對象,進行獲取到對應的參數。命令行指定參數時有兩種參數,一種是可選型參數、一種是非可選型參數,可選型參數以--開頭,需要賦值時可以加上=,比如指定命令行參數爲--debug --foo=bar abc,則可選型參數爲debug和foo,而非可選型參數爲abc。如下代碼就是基於該命令行參數的一個簡單示例。

@Controller
public class SampleController {

    @Autowired
    private ApplicationArguments arguments;
    
    /**
     * 傳遞的命令行參數是--debug --foo=bar abc
     * @param writer
     * @throws Exception
     */
    @GetMapping("sample/args")
    public void arguments(PrintWriter writer) throws Exception {
        writer.println("包含debug參數:" + arguments.containsOption("debug"));//true
        writer.println("參數foo的值是:" + arguments.getOptionValues("foo"));//[bar]
        writer.println("其它非選項性參數:" + arguments.getNonOptionArgs());//[abc]
        writer.println("原始參數是:" + Arrays.toString(arguments.getSourceArgs()));//--debug, --foo=bar, abc
    }

}

這種參數有別於在運行程序時通過-Dkey=value指定的虛擬機參數,通過-Dkey=value指定的虛擬機參數可以通過System.getProperty("key")獲取到。命令行參數是對應程序運行主命令之後添加的參數,比如上面添加的那些參數的完整指令是java -jar app.jar --debug --foo=bar abc

ApplicationRunner和CommandLineRunner

前面在介紹事件監聽器的時候已經介紹了,在Spring Boot應用啓動成功後會在bean容器中尋找ApplicationRunner和CommandLineRunner類型的bean,調用它們的run()。所以如果想在Spring Boot應用啓動成功或做一些事情,則可以實現自己的ApplicationRunner或CommandLineRunner。它們的區別在於ApplicationRunner的run()的入參是ApplicationArguments對象,而CommandLineRunner的run()的入參是原始的參數數組。

@Component
@Slf4j
public class MyApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("Spring Boot應用啓動成功,攜帶的命令行參數是:{}", Arrays.toString(args.getSourceArgs()));
    }

}

@Component
@Slf4j
public class MyCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        log.info("Spring Boot應用已經啓動成功了,攜帶的命令行參數是:{}", Arrays.toString(args));
    }

}

其實前面介紹事件監聽器的時候也提到了,通過實現ApplicationListener,監聽ApplicationStartedEvent或ApplicationReadyEvent也可以在Spring Boot應用啓動成功後做一些事情。它們的區別主要就在於ApplicationRunner和CommandLineRunner實現類是bean容器中的一個bean,可以注入其它bean,而且它們可以很方便的訪問到命令行參數。

SpringApplicationBuilder

在構建SpringApplication對象時也可以通過SpringApplicationBuilder進行構建,通過它可以流式的進行配置,還可以指定子ApplicationContext。

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        // SpringApplication.run(SpringBootApplication.class, args);
        SpringApplication app = new SpringApplicationBuilder(Application.class)
            .child(ChildConfig.class)
            .bannerMode(Banner.Mode.OFF)
            .build();
        app.run(args);
    }

}

關於SpringApplication的更多可定製的信息可以參考對應的API文檔。

啓用JMX管理

在application.properties文件中添加spring.application.admin.enabled=true可以啓用JMX管理,這會發佈一個SpringApplicationAdminMXBean類型的MBean。通過它的getProperty()可以獲取當前應用對應的啓動JVM的一些系統屬性或者是定義在application.properties中的一些屬性的值,因爲其底層對應的是當前Environment對象。通過其shutdown()可以進行遠程的關閉操作。

參考文檔

https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/html/boot-features-spring-application.html

(注:本文是基於Spring Boot 2.0.3所寫)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章