首頁 > 軟體

SpringBoot工程啟動順序與自定義監聽超詳細講解

2022-11-11 14:00:03

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執行refresh之前,呼叫ApplicationContextInitializer的initialize()方法,對ApplicationContext做進一步的設定和處理
  • SpringApplicationRunListener:SpringBoot只提供一個實現類EventPublishingRunListener,在SpringBoot啟動過程中,負責註冊ApplicationListener監聽器,在不同的時點發布不同的事件型別,如果有哪些ApplicationListener的實現類監聽了這些事件,則可以接收並處理
  • ApplicationListener:事件監聽器,其作用可以理解為在SpringApplicationRunListener釋出通知事件時,由ApplicationListener負責接收

啟動順序說明

建構函式:初始化web容器,載入ApplicationContextInitializer的實現類並將其範例化,載入ApplicationListener的實現類並將其範例化

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的實現類並將其範例化
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	//找到*META-INF/spring.factories*中宣告的所有ApplicationListener的實現類並將其範例化
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	//獲得當前執行main方法的類物件
	this.mainApplicationClass = deduceMainApplicationClass();
}

啟動方法

public ConfigurableApplicationContext run(String... args) {
	long startTime = System.nanoTime();
	// 建立bootstrap上下文
	DefaultBootstrapContext bootstrapContext = createBootstrapContext();
	ConfigurableApplicationContext context = null;
	configureHeadlessProperty();
		//通過*SpringFactoriesLoader*檢索*META-INF/spring.factories*,
		//找到宣告的所有SpringApplicationRunListener的實現類並將其範例化,
		//之後逐個呼叫其started()方法,廣播SpringBoot要開始執行了。
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting(bootstrapContext, this.mainApplicationClass);
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		//建立並設定當前SpringBoot應用將要使用的Environment(包括設定要使用的PropertySource以及Profile),
		//並遍歷呼叫所有的SpringApplicationRunListener的environmentPrepared()方法,廣播Environment準備完畢。
		ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
		//決定是否列印Banner
		configureIgnoreBeanInfo(environment);
		Banner printedBanner = printBanner(environment);
		//根據webApplicationType的值來決定建立何種型別的ApplicationContext物件
		//如果是SERVLET環境,則建立AnnotationConfigServletWebServerApplicationContext
		//如果是REACTIVE環境,則建立AnnotationConfigReactiveWebServerApplicationContext
		//否則建立AnnotationConfigApplicationContext
		context = createApplicationContext();
		context.setApplicationStartup(this.applicationStartup);
		//為ApplicationContext載入environment,之後逐個執行ApplicationContextInitializer的initialize()方法來進一步封裝ApplicationContext,
		//並呼叫所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一個空的contextPrepared()方法】,
		//之後初始化IoC容器,並呼叫SpringApplicationRunListener的contextLoaded()方法,廣播ApplicationContext的IoC載入完成,
		//這裡就包括通過**@EnableAutoConfiguration**匯入的各種自動設定類。
		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		//初始化所有自動設定類,呼叫ApplicationContext的refresh()方法
		refreshContext(context);
		//目前該方法為空
		afterRefresh(context, applicationArguments);
		Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
		}
		//呼叫所有的SpringApplicationRunListener的started()方法,廣播SpringBoot已經完成了ApplicationContext初始化的全部過程。
		listeners.started(context, timeTakenToStartup);
		//遍歷所有註冊的ApplicationRunner和CommandLineRunner,並執行其run()方法。
		//該過程可以理解為是SpringBoot完成ApplicationContext初始化前的最後一步工作,
		//我們可以實現自己的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

我們的程式經常需要在啟動過程中或啟動完成後做一些額外的邏輯處理,那麼可以通過以下三種方式處理:

1、建立ApplicationContextInitializer的實現類

1)建立實現類

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)或者修改啟動方法,呼叫addInitializers新增

public static void main(String[] args) {
    SpringApplication springApplication = new SpringApplication(Application.class);
    springApplication.addInitializers(new MyApplicationContextInitializer());
    springApplication.run(args);
}

2、建立ApplicationListener的實現類

ApplicationListener也有兩種方式,首先建立實現類,然後修改啟動方法,呼叫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實現

# Application Listeners
org.springframework.context.ApplicationListener=
com.hsoft.demo.MyApplicationListener

推薦直接使用註解@ComponentaddListeners()方式,如果設定META-INF/spring.factories,因bootstrap設定分開載入所以監聽程式會被觸發兩次

3、建立ApplicationRunner和CommandLineRunner的實現類

只需建立一個實現型別,並在實現類上面增加註解@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);
    }
}

到此這篇關於SpringBoot工程啟動順序與自定義監聽超詳細講解的文章就介紹到這了,更多相關SpringBoot工程啟動順序內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com