SpringBoot工程啟動順序與自定義監(jiān)聽超詳細講解
SpringBoot在2.4版本以后默認不加載bootstrap.yml配置項。
如果需要加載該配置項,需要引入依賴,通常Spring Cloud工程配合nacos這種配置中心或注冊中心使用時,需要引入該依賴。
SpringBoot單體工程無需引入該依賴,所有配置放在application.yml中即可。
<!-- bootstrap 啟動器 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency>
配置項
SpringBoot工程在啟動時,會通過SpringFactoriesLoader檢索META-INF/spring.factories文件,并加載其中的配置項。
常見的配置項有如下幾種:
- ApplicationContextInitializer:為在ApplicationContext執(zhí)行refresh之前,調(diào)用ApplicationContextInitializer的initialize()方法,對ApplicationContext做進一步的設(shè)置和處理
- SpringApplicationRunListener:SpringBoot只提供一個實現(xiàn)類EventPublishingRunListener,在SpringBoot啟動過程中,負責(zé)注冊ApplicationListener監(jiān)聽器,在不同的時點發(fā)布不同的事件類型,如果有哪些ApplicationListener的實現(xiàn)類監(jiān)聽了這些事件,則可以接收并處理
- ApplicationListener:事件監(jiān)聽器,其作用可以理解為在SpringApplicationRunListener發(fā)布通知事件時,由ApplicationListener負責(zé)接收
啟動順序說明
構(gòu)造函數(shù):初始化web容器,加載ApplicationContextInitializer的實現(xiàn)類并將其實例化,加載ApplicationListener的實現(xiàn)類并將其實例化
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 初始化web容器類型,默認SERVLET,如果存在org.springframework.web.reactive.DispatcherHandler,則是REACTIVE this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.bootstrapRegistryInitializers = new ArrayList<>( getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); //找到*META-INF/spring.factories*中聲明的所有ApplicationContextInitializer的實現(xiàn)類并將其實例化 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //找到*META-INF/spring.factories*中聲明的所有ApplicationListener的實現(xiàn)類并將其實例化 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //獲得當前執(zhí)行main方法的類對象 this.mainApplicationClass = deduceMainApplicationClass(); }
啟動方法
public ConfigurableApplicationContext run(String... args) { long startTime = System.nanoTime(); // 創(chuàng)建bootstrap上下文 DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); //通過*SpringFactoriesLoader*檢索*META-INF/spring.factories*, //找到聲明的所有SpringApplicationRunListener的實現(xiàn)類并將其實例化, //之后逐個調(diào)用其started()方法,廣播SpringBoot要開始執(zhí)行了。 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //創(chuàng)建并配置當前SpringBoot應(yīng)用將要使用的Environment(包括配置要使用的PropertySource以及Profile), //并遍歷調(diào)用所有的SpringApplicationRunListener的environmentPrepared()方法,廣播Environment準備完畢。 ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); //決定是否打印Banner configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); //根據(jù)webApplicationType的值來決定創(chuàng)建何種類型的ApplicationContext對象 //如果是SERVLET環(huán)境,則創(chuàng)建AnnotationConfigServletWebServerApplicationContext //如果是REACTIVE環(huán)境,則創(chuàng)建AnnotationConfigReactiveWebServerApplicationContext //否則創(chuàng)建AnnotationConfigApplicationContext context = createApplicationContext(); context.setApplicationStartup(this.applicationStartup); //為ApplicationContext加載environment,之后逐個執(zhí)行ApplicationContextInitializer的initialize()方法來進一步封裝ApplicationContext, //并調(diào)用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一個空的contextPrepared()方法】, //之后初始化IoC容器,并調(diào)用SpringApplicationRunListener的contextLoaded()方法,廣播ApplicationContext的IoC加載完成, //這里就包括通過**@EnableAutoConfiguration**導(dǎo)入的各種自動配置類。 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); //初始化所有自動配置類,調(diào)用ApplicationContext的refresh()方法 refreshContext(context); //目前該方法為空 afterRefresh(context, applicationArguments); Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup); } //調(diào)用所有的SpringApplicationRunListener的started()方法,廣播SpringBoot已經(jīng)完成了ApplicationContext初始化的全部過程。 listeners.started(context, timeTakenToStartup); //遍歷所有注冊的ApplicationRunner和CommandLineRunner,并執(zhí)行其run()方法。 //該過程可以理解為是SpringBoot完成ApplicationContext初始化前的最后一步工作, //我們可以實現(xiàn)自己的ApplicationRunner或者CommandLineRunner,來對SpringBoot的啟動過程進行擴展。 callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime); listeners.ready(context, timeTakenToReady); } catch (Throwable ex) { handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context; }
擴展SpringApplication
我們的程序經(jīng)常需要在啟動過程中或啟動完成后做一些額外的邏輯處理,那么可以通過以下三種方式處理:
1、創(chuàng)建ApplicationContextInitializer的實現(xiàn)類
1)創(chuàng)建實現(xiàn)類
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void initialize(ConfigurableApplicationContext applicationContext) { logger.info("MyApplicationContextInitializer, {}", applicationContext.getApplicationName()); } }
2)配置META-INF/spring.factories
org.springframework.context.ApplicationContextInitializer=\ com.hsoft.demo.MyApplicationContextInitializer
3)或者修改啟動方法,調(diào)用addInitializers添加
public static void main(String[] args) { SpringApplication springApplication = new SpringApplication(Application.class); springApplication.addInitializers(new MyApplicationContextInitializer()); springApplication.run(args); }
2、創(chuàng)建ApplicationListener的實現(xiàn)類
ApplicationListener也有兩種方式,首先創(chuàng)建實現(xiàn)類,然后修改啟動方法,調(diào)用addListeners
添加,或者直接添加注解@Component
@Component public class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> { private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void onApplicationEvent(ApplicationReadyEvent event) { logger.info("MyApplicationListener,{}",event.toString()); } }
public static void main(String[] args) { SpringApplication springApplication = new SpringApplication(Application.class); springApplication.addListeners(new MyApplicationListener()); springApplication.run(args); }
也可以通過配置META-INF/spring.factories實現(xiàn)
# Application Listeners
org.springframework.context.ApplicationListener=\
com.hsoft.demo.MyApplicationListener
推薦直接使用注解@Component
或addListeners()
方式,如果配置META-INF/spring.factories,因bootstrap配置分開加載所以監(jiān)聽程序會被觸發(fā)兩次
3、創(chuàng)建ApplicationRunner和CommandLineRunner的實現(xiàn)類
只需創(chuàng)建一個實現(xiàn)類型,并在實現(xiàn)類上面增加注解@Component即可
@Component public class MyApplicationRunner implements ApplicationRunner { private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void run(ApplicationArguments args) throws Exception { logger.info("MyApplicationRunner, {}",args.getOptionNames()); } }
@Component public class MyCommandLineRunner implements CommandLineRunner { private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void run(String... args) throws Exception { logger.info("MyCommandLineRunner, {}", args); } }
生成war包在web容器(tomcat)中部署
如果SpringBoot工程要在Tomcat中部署,需要通過如下操作:
1、修改成war工程
2、嵌入式Tomcat依賴scope指定provided
3、編寫SpringBootServletInitializer類子類,并重寫configure方法
/** * web容器中進行部署 * */ public class MyServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(MyApplication.class); } }
到此這篇關(guān)于SpringBoot工程啟動順序與自定義監(jiān)聽超詳細講解的文章就介紹到這了,更多相關(guān)SpringBoot工程啟動順序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
k8s部署springboot實現(xiàn)前后端分離項目
本文主要介紹了k8s部署springboot實現(xiàn)前后端分離項目,主要包括配置文件、鏡像構(gòu)建和容器編排等方面,具有一定的參考價值,感興趣的可以了解一下2024-01-01SpringBoot如何使用feign實現(xiàn)遠程接口調(diào)用和錯誤熔斷
這篇文章主要介紹了SpringBoot如何使用feign實現(xiàn)遠程接口調(diào)用和錯誤熔斷,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-12-12關(guān)于Maven依賴沖突解決之exclusions
這篇文章主要介紹了關(guān)于Maven依賴沖突解決之exclusions用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12Java中使用ConcurrentHashMap實現(xiàn)線程安全的Map
在Java中,ConcurrentHashMap是一種線程安全的哈希表,可用于實現(xiàn)多線程環(huán)境下的Map操作。它支持高并發(fā)的讀寫操作,通過分段鎖的方式實現(xiàn)線程安全,同時提供了一些高級功能,比如迭代器弱一致性和批量操作等。ConcurrentHashMap在高并發(fā)場景中具有重要的應(yīng)用價值2023-04-04Java中final,finally,finalize?有什么區(qū)別
這篇文章主要給大家分享的是?Java中final,finally,finalize?到底有什么區(qū)別,文章圍繞final,finally,finalize的相關(guān)資料展開詳細內(nèi)容,具有一定的參考的價值,需要的朋友可以參考一下2021-11-11Java調(diào)用Shell命令和腳本的實現(xiàn)
這篇文章主要介紹了Java調(diào)用Shell命令和腳本的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02