SpringBoot原理總結幾個重要的事件回撥機制ApplicationContextInitializer使用分析SpringBoot內建的ApplicationContextInitializer擴充套件實現方式程式設計方式新增配置方式spring.factories方式啟動流程事件監聽機制SpringBoot自定義starter總結幾個重要的事件回撥機制ApplicationContextInitializerApplicationContextInitializer來源於Spring框架主要作用就是在ConfigurableApplicationContext型別或者子型別的ApplicationContext做refresh之前允許對ConfigurableApplicationContext的例項做進一步的設定和處理ApplicationContextInitializer介面:是在Spring容器重新整理之前執行的一個回撥函式是在ConfigurableApplicationContext的refresh() 方法之前,即在Spring框架內部執行ConfigurableApplicationContext的refresh() 方法或者SpringBoot的run() 方法之前呼叫作用是初始化Spring的ConfigurableApplicationContext的回撥介面通常用於需要對應用上下文進行初始化的web應用程式中: 比如根據上下文環境註冊屬性或者啟用概要檔案使用分析ApplicationContextInitializer介面的典型應用場景:對web應用程式的應用上下文進行初始化比如:註冊屬性源property sources針對上下文的環境資訊environment啟用相應的profile在一個SpringBoot的應用程式中:classpath上有很多jar包,有些jar包需要在ConfigurableApplicationContext的refresh() 方法呼叫之前對應用上下文做一些初始化動作因此會提供自己的ApplicationContextInitializer實現類,然後配置在自己的META-INF/spring.factories屬性檔案中這樣相應的ApplicationContextInitializer實現類就會被SpringApplication的initialize() 方法發現SpringApplication的initialize() 方法,在SpringApplication的建構函式內執行,從而確保在SpringApplication的run() 方法之前完成然後在應用上下文建立之後,應用上下文重新整理之前的準備階段被呼叫SpringBoot內建的ApplicationContextInitializer使用SpringBoot web應用預設使用的ApplicationContextInitializer的實現:DelegatingApplicationContextInitializer:使用環境屬性context.initializer.classes指定的初始化容器initializer進行初始化工作,如果沒有指定則不進行任何操作使得可以在application.properties中可以自定義實現類配置ContextIdApplicationContextInitializer:參照環境屬性,設定Spring應用上下文的IDID值的設定會參照環境屬性:spring.application.namevcap.application.namespring.config.namespring.application.indexvcap.application.instance_index如果這些屬性都沒有 ,ID使用applicationConfigurationWarningApplicationContextInitializer:對於一般配置錯誤在日誌中做出警告ServerPortInfoApplicationContextInitializer:將內建servlet容器實際使用的監聽埠寫入到environment環境屬性中這樣屬性local.server.port就可以直接透過 @Value注入到測試中或者透過環境屬性environment獲取SharedMetadataReaderFactoryContextInitializer:建立一個SpringBoot和ConfigurationClassPostProcessor共用的CachingMetadataReaderFactory物件實現類為ConcurrentReferrenceCachingMetadataReaderFactoryConditionEvaluationReportLoggingListener:將ConditionEvaluationReport寫入日誌ApplicationContextInitializer是Spring中允許在上下文重新整理之前做自定義操作,如果需要對Spring的上下文進行深度整合,可以藉助ApplicationContextInitializer進行很好的實現spring-test包裡有一個註解org.springframework.test.context.ContextConfiguration中有一個屬性可以指定ApplicationContextInitializer輔助整合測試時自定義對上下文進行預處理擴充套件實現方式程式設計方式先定義ApplicationContextInitializer:
// @Order(66) - @Order的值越小就越早執行. 標註在類上, 不是方法上@Order(66)public class customerApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { // 輸出容器中有多少個bean System.out.println("Bean的數量為: " + applicationContext.getBeanDefinitionCount()); /* * 輸出容器中所有bean的beanName */ System.out.println(applicationContext.getBeanDefinitionCount + "個Bean的名稱:"); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String beanName : beanDefinitionNames) { System.out.println(beanName); } }}
在啟動類中手動增加initializer:
@SpringBootApplication@EnableConfigServer@EnableDiscoveryClientpublic class ConfigServer { public static void mian(String[] args) { SpringApplication springApplication = new SpringApplication(ConfigServer.class); // 新增自定義的ApplicationContextInitializer實現類的例項用來註冊ApplicationContextInitializer springApplication.addInitializers(new customerApplicationContextInitializer()); ConfigurableApplicationContext applicationContext = springApplication.run(args); applicationContext.close(); }}
新增配置方式新增配置的方式是透過DelegatingApplicationContextInitializer初始化類中的initialize() 方法獲取到application.properties中context.initializer.class對應的類並執行對應的initialize() 方法只需要將實現了ApplicationContextInitializer的類新增到application.properties即可先定義一個實現了ApplocationContextInitializer的類然後在application.properties中定義:context.initializer.class= com.oxford.customerApplicationContextInitializerspring.factories方式SpringApplicationRunListenerApplicationContextInitializer,SpringApplicationRunListener需要配置在META-INF/spring.factories中ApplicationRunnerCommandLineRunnerApplicationRunner,CommandLineRunner需要放在IOC容器中啟動流程建立SpringApplication物件呼叫initialize(sources)方法創造物件儲存主配置類判斷當前是否為一個web應用從類路徑下找到META-INF/spring.factories配置的所有ApplicationContextInitializer,然後儲存起來從從類路徑下找到META-INF/spring.factories配置的所有ApplicatListener從多個配置類中找到有main方法的主配置類執行run方法public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 停止監聽 ConfigurableApplicationContext context = null; // 宣告一個IOC容器 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); this.configureHeadlessProperty(); SpringApplicationRunListeners listeners = this.getRunListeners(args); listeners.starting(); Collection exceptionReporters; try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); this.configureIgnoreBeanInfo(environment); Banner printedBanner = this.printBanner(environment); context = this.createApplicationContext(); exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); this.refreshContext(context); this.afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } listeners.started(context); this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, exceptionReporters, listeners); throw new IllegalStateException(var10); } try { listeners.running(context); return context; } catch (Throwable var9) { this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); } }
獲取SpringApplicationRunListeners,從類路徑下META-INF/spring.factories回撥所有的獲取SpringApplicationRunListener.starting()方法封裝命令列引數準備環境prepareEnvironment,建立環境完成後回撥SpringApplicationRunListeners.environmentPrepared():表示環境準備完成private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { ConfigurableEnvironment environment = this.getOrCreateEnvironment(); this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs()); listeners.environmentPrepared((ConfigurableEnvironment)environment); this.bindToSpringApplication((ConfigurableEnvironment)environment); if (!this.isCustomEnvironment) { environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass()); } ConfigurationPropertySources.attach((Environment)environment); return (ConfigurableEnvironment)environment; }
建立ApplicationContext:決定建立web的IOC還是普通的IOCprotected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch(this.webApplicationType) { case SERVLET: contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext"); break; case REACTIVE: contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext"); break; default: contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext"); } } catch (ClassNotFoundException var3) { throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3); } } return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass); }
準備上下文環境prepareContext:將environment儲存到IOC中,並且呼叫applyInitializers():回撥之前儲存的所有的ApplicationContextInitializer的initialize方法.然後回撥SpringApplicationRunListener的contextPrepared方法private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); this.postProcessApplicationContext(context); this.applyInitializers(context); listeners.contextPrepared(context); if (this.logStartupInfo) { this.logStartupInfo(context.getParent() == null); this.logStartupProfileInfo(context); } ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } Set<Object> sources = this.getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); this.load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); }
prepareContext執行完成以後回撥所有的SpringApplicationRunListeners的contextLoaded()方法重新整理容器refreshContext,IOC容器初始化.在web應用中還會建立嵌入式的tomcat.在refreshContext,是掃描,建立.載入所有元件的地方(配置類,元件,自動配置)private void refreshContext(ConfigurableApplicationContext context) { this.refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException var3) { } } }
呼叫callRunner()從IOC容器中獲取所有的ApplicationRunner和CommandLineRunner.先回調ApplicationRunner,後回撥CommandLineRunner最後回撥SpringApplicationRunListeners的listeners.running(context)整個SpringBoot應用啟動完成以後返回啟動的IOC容器事件監聽機制ApplicationContextInitializer
public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext > { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { System.out.println("ApplicationContextInitializer...initialize"+configurableApplicationContext); }}
SpringApplicationRunListenerpublic class HelloSpringApplicationRunListener implements SpringApplicationRunListener { @Override public void starting() { System.out.println("SpringApplicationRunListener...starting..."); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { Object o=environment.getSystemProperties().get("os.name"); System.out.println("SpringApplicationRunListener...environmentPrepared..."); } @Override public void contextPrepared(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...contextPrepared..."); } @Override public void contextLoaded(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...contextLoaded..."); } @Override public void started(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...started..."); } @Override public void running(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...running..."); } @Override public void failed(ConfigurableApplicationContext context, Throwable exception) { System.out.println("SpringApplicationRunListener...failed..."); }}
ApplicationContextInitializer,SpringApplicationRunListener需要配置在META-INF/spring.factories中org.springframework.context.ApplicationContextInitializer=\com.web.springboot.listener.HelloApplicationContextInitializerorg.springframework.context.SpringApplicationRunListener=\com.web.springboot.listener.HelloSpringApplicationRunListener
ApplicationRunner
@Component // 容器中的類public class HelloApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("ApplicationRunner...run..."); }}
CommandLineRunner
@Component // 容器中的類public class HelloCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("CommandLineRunner...run"+ Arrays.asList(args)); }}
ApplicationRunner,CommandLineRunner需要放在IOC容器中-@ComponentSpringBoot自定義starterstarter:這個場景需要的依賴是什麼?如何編寫自動配置?@Configuration // 指定這個類是一個自動配置類 @ConditionalOnXxx() // 在指定條件成立的情況下自動配置類生效 @AutoConfigureOrder() // 指定自動配置類的順序 @AutoConfigureAfter() // 指定自動配置在特定的類之後 @Bean // 給容器中新增元件 ( @ConfigurationProperties 結合相關 XxxProperties類來繫結相關的配置 ) @EnableConfigurationProperties // 讓XxxProperties類生效加入到容器中 配置自動裝配Bean:將標註@Configuration的自動配置類,放在classpath下的META-INF/spring.factories檔案中才能載入org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener 模式:啟動器: 啟動器是一個空jar檔案,僅提供輔助性依賴管理,依賴匯入,這些依賴用於自動裝配或者其它類庫.官方名稱空間: - 字首: spring-boot-starter- - 模式: spring-boot-starter-模組名 自定義名稱空間: - 字首: -spring-boot-starter- - 模式: 模組名-spring-boot-starter 專門寫一個自動配置模組啟動器依賴自動配置模組,使用時只需要引入啟動器( starter )總結官方文件原始碼