本文主要介紹SpringBoot—項目啟動時幾種初始化操作及SpringApplication類詳解。
關注微信公衆号:CodingTechWork,一起學習進步。
引言
在使用Spring Boot搭建項目時,啟動項目工程,經常遇到一些需要啟動初始化資料或者資源的需求,比如提前加載某個配置檔案内容,初始化某個資訊、做好安全認證等。這裡一起學習總結了幾種初始化資料的方式。
@Bean注解配置
使用方式
編寫配置類,使用
@Configuration
和
@Bean
注解進行初始化。
使用示例
package com.example.andya.demo.conf;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Andya
* @create 2020-09-14 21:37
*/
@Configuration
public class InitConfigTest {
@Value("${key}")
private String key;
@Bean
public String testInit(){
System.out.println("init key: " + key);
return key;
}
}
ApplicationRunner接口
編寫類去實作
ApplicationRunner接口
,實作
run()
方法,該方法在工程啟動類的
XXXApplication
的
SpringApplication.run(xxxApplication.class, args)
方法之前,
@Componet
會在所有Spring的
Beans
初始化完成之後,執行完成。
package com.example.andya.demo.service.initTest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* @author Andya
* @date 2020-09-14 21:37
*/
@Component
public class ApplicationRunnerTest implements ApplicationRunner {
@Value("${key}")
private String key;
@Override
public void run(ApplicationArguments applicationArguments) throws Exception {
System.out.println("ApplicationRunner test: init key : " + key);
}
}
CommandLineRunner接口
類似于ApplicationRunner接口,我們同樣編寫類去實作
CommandLineRunner接口
run()
XXXApplication
SpringApplication.run(xxxApplication.class, args)
@Componet
Beans
多個類實作接口後,可以通過
@Order
注解進行順序的控制,
@Order(n)
,n越小,啟動執行的越早。
示例1
package com.example.andya.demo.service.initTest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author Andya
* @create 2020-09-14 21:37
*/
@Component
@Order(1)
public class CommandLineRunner1Test implements CommandLineRunner {
@Value("${key}")
private String key;
@Override
public void run(String... strings) throws Exception {
System.out.println("CommandLineRunner first init: 1)init key : " + key);
}
}
示例2
package com.example.andya.demo.service.initTest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author Andya
* @create 2020-09-14 21:37
*/
@Component
@Order(2)
public class CommandLineRunner2Test implements CommandLineRunner {
@Value("${key}")
private String key;
@Override
public void run(String... strings) throws Exception {
System.out.println("CommandLineRunner second init: 2)init key : " + key);
}
}
兩種接口分析
接口對比
CommandLineRunner
ApplicationRunner
都是接口,隻是内部參數不一樣,前者的參數是最原始的參數String類型,無任何處理;後者是
ApplicationArguments
類型,對原始參數進行了封裝處理。
執行結果
上述三種方式的執行結果示例
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.6.RELEASE)
2020-09-14 21:45:28.403 INFO 25408 --- [ main] com.example.andya.demo.DemoApplication : Starting DemoApplication on DESKTOP-KC40970 with PID 25408 (F:\selfcode\target\classes started by Hugh in F:\selfcode)
2020-09-14 21:45:28.406 INFO 25408 --- [ main] com.example.andya.demo.DemoApplication : No active profile set, falling back to default profiles: default
2020-09-14 21:45:28.434 INFO 25408 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1542153: startup date [Tue Sep 15 11:28:28 CST 2020]; root of context hierarchy
2020-09-14 21:45:29.322 INFO 25408 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 9000 (http)
2020-09-14 21:45:29.327 INFO 25408 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-09-14 21:45:29.327 INFO 25408 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.16
2020-09-14 21:45:29.385 INFO 25408 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-09-14 21:45:29.385 INFO 25408 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 953 ms
2020-09-14 21:45:29.504 INFO 25408 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2020-09-14 21:45:29.506 INFO 25408 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2020-09-14 21:45:29.506 INFO 25408 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2020-09-14 21:45:29.506 INFO 25408 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2020-09-14 21:45:29.506 INFO 25408 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
init key: value
2020-09-14 21:45:29.818 INFO 25408 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1542153: startup date [Tue Sep 15 11:28:28 CST 2020]; root of context hierarchy
2020-09-14 21:45:29.857 INFO 25408 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/aopTest/sayHi/{name}],methods=[GET]}" onto public java.lang.String com.example.andya.demo.controller.AopController.sayHi(java.lang.String)
2020-09-14 21:45:29.857 INFO 25408 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/hello],methods=[GET]}" onto public java.lang.String com.example.andya.demo.controller.HelloController.hello()
2020-09-14 21:45:29.859 INFO 25408 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2020-09-14 21:45:29.859 INFO 25408 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2020-09-14 21:45:29.923 INFO 25408 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-09-14 21:45:29.923 INFO 25408 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-09-14 21:45:29.955 INFO 25408 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-09-14 21:45:30.064 INFO 25408 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2020-09-14 21:45:30.069 INFO 25408 --- [ main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2020-09-14 21:45:30.095 INFO 25408 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 9000 (http)
CommandLineRunner first init: 1)init key : value
CommandLineRunner second init: 2)init key : value
ApplicationRunner test: init key : value
2020-09-14 21:45:30.097 INFO 25408 --- [ main] com.example.andya.demo.DemoApplication : Started DemoApplication in 1.911 seconds (JVM running for 2.601)
2020-09-14 21:46:00.004 INFO 25408 --- [pool-1-thread-1] .s.a.AnnotationAsyncExecutionInterceptor : No task executor bean found for async processing: no bean of type TaskExecutor and no bean named 'taskExecutor' either
2020-09-14 21:46:59.781 INFO 25408 --- [nio-9000-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2020-09-14 21:46:59.781 INFO 25408 --- [nio-9000-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2020-09-14 21:46:59.802 INFO 25408 --- [nio-9000-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 21 ms
源碼分析
XxxApplication啟動類
package com.example.andya.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
其中,在工程中,點選
SpringApplication.run(DemoApplication.class, args);
的run方法進行源碼追蹤。
SpringApplication類源碼
//構造函數1
public SpringApplication(Object... sources) {
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.initialize(sources);
}
//構造函數2
public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.resourceLoader = resourceLoader;
this.initialize(sources);
}
//initialize方法
private void initialize(Object[] sources) {
//source不為空時,儲存配置類
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//判斷該應用是否為web應用
this.webEnvironment = this.deduceWebEnvironment();
//擷取并儲存容器初始化ApplicationContextInitializer類,通過SpringFactoriesLoader.loadFactoryNames方法
//從META-INF/spring.factories路徑中擷取ApplicationContextInitializer集合
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//擷取并儲存監聽器 ApplicationListener類,同樣的,通過SpringFactoriesLoader.loadFactoryNames方法
//從META-INF/spring.factories路徑中擷取ApplicationListener集合
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//通過堆棧追蹤名為main方法,進而擷取包含main方法的類
this.mainApplicationClass = this.deduceMainApplicationClass();
}
//run()方法1
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[]{source}, args);
}
//run()方法2
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return (new SpringApplication(sources)).run(args);
}
//run()方法3,最底層
public ConfigurableApplicationContext run(String... args) {
//建立計時工具類StopWatch
StopWatch stopWatch = new StopWatch();
//啟動計時工具類StopWatch
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
//設定java.awt.headless的系統屬性,如伺服器不需要顯示器就需要如此設定
this.configureHeadlessProperty();
//擷取監聽器SpringApplicationRunListeners,調用了getSpringFactoriesInstances()方法
//該方法又調用了loadFactoryNames()方法從META-INF/spring.factories路徑中擷取SpringApplicationRunListeners集合
SpringApplicationRunListeners listeners = this.getRunListeners(args);
//啟動監聽器
listeners.starting();
try {
//将args參數封裝至ApplicationArguments中
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//準備環境:内部調用了this.configureEnvironment()和listeners.environmentPrepared()等方法
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
//從環境中擷取Banner進行列印,可以自定義Banner
Banner printedBanner = this.printBanner(environment);
//建立Spring的容器
context = this.createApplicationContext();
//分析并診斷是項目啟動否有問題
new FailureAnalyzers(context);
//準備容器上下文:
//1)設定容器環境:context.setEnvironment(environment);
//2)設定beanNameGenerator和resourceLoader:this.postProcessApplicationContext(context);
//3)初始化context并檢測是否接受該類型容器:this.applyInitializers(context);
//4)觸發監聽事件:listeners.contextPrepared(context);是一個空函數;
//5)注冊bean:通過context.getBeanFactory().registerSingleton()方法向容器注入springApplicationArguments和springBootBanner
//6)擷取sources:Set<Object> sources = this.getSources();
//7)加載啟動類,注入到容器内:this.load(context, sources.toArray(new Object[sources.size()]));
//給loader設定beanNameGenerator,resourceLoader和environment
//然後調用load()方法,按照不同的source類型加載class,resource,package,charSequence,超過這些範圍的就抛出異常“Invalid source type xxx”
//8)觸發監聽事件:listeners.contextLoaded(context);進行
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//重新整理Spring容器
this.refreshContext(context);
//從容器中擷取所有的ApplicationRunner和CommandLineRunner進行回調callRunner
this.afterRefresh(context, applicationArguments);
//觸發監聽事件,所有的SpringApplicationRunListener進行callFinishedListener回調
listeners.finished(context, (Throwable)null);
//計時器停止
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
//整個SpringBoot工程啟動完畢,傳回啟動的Ioc容器上下文
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
throw new IllegalStateException(var9);
}
}
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
this.callRunners(context, args);
}
//callRunners
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
Iterator var4 = (new LinkedHashSet(runners)).iterator();
while(var4.hasNext()) {
Object runner = var4.next();
if (runner instanceof ApplicationRunner) {
this.callRunner((ApplicationRunner)runner, args);
}
if (runner instanceof CommandLineRunner) {
this.callRunner((CommandLineRunner)runner, args);
}
}
}
在上述源碼中,我們分析了SpringBoot啟動的一個流程,其中,我們可以從源碼的
run()
方法一層一層點選追蹤檢視到,
ApplicationRunner
CommandLineRunner
是在
callRunners
方法中執行的。
參考
jdk1.8
燒不死的鳥就是鳳凰