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()
可以進行遠端的關閉操作。
參考文件
(注:本文是基於Spring Boot 2.0.3所寫)