在Spring boot 開發(fā)中育瓜,經(jīng)常遇到一些注解,類方法栽烂;參數(shù)以及啟動參數(shù)躏仇,先后順序以及執(zhí)行的方式,有點分不清腺办;這篇文章介紹一下Spring boot的啟動邏輯焰手;
maven 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
Spring boot的啟動相關接口
-
ApplicationContextInitializer 主要是在Spring context啟動之前,注冊property sources文件菇晃,設置 environment相關的參數(shù)設置;同時可以通過ordered進行優(yōu)先級的設置蚓挤;下面附帶源碼介紹
//主要是在Spring context啟動之前磺送,注冊property sources文件,設置 environment相關的參數(shù)設置灿意;同時可以通過ordered進行優(yōu)先級的設置估灿;下面附帶源碼介紹 * Callback interface for initializing a Spring {@link ConfigurableApplicationContext} * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}. * * <p>Typically used within web applications that require some programmatic initialization * of the application context. For example, registering property sources or activating * profiles against the {@linkplain ConfigurableApplicationContext#getEnvironment() * context's environment}. See {@code ContextLoader} and {@code FrameworkServlet} support * for declaring a "contextInitializerClasses" context-param and init-param, respectively. * * <p>{@code ApplicationContextInitializer} processors are encouraged to detect * whether Spring's {@link org.springframework.core.Ordered Ordered} interface has been * implemented or if the @{@link org.springframework.core.annotation.Order Order} * annotation is present and to sort instances accordingly if so prior to invocation.
-
SpringApplicationRunListener 監(jiān)聽Spring application run方法,可以通過SpringFactoriesLoader 加載相關的資源缤剧;而且必須聲明公共的構(gòu)造器來接受Springapplication 以及args 參數(shù)馅袁;
//監(jiān)聽Spring application run方法,可以通過SpringFactoriesLoader 加載相關的資源荒辕;而且必須聲明公共的構(gòu)造器來接受Springapplication 以及args 參數(shù)汗销; * Listener for the {@link SpringApplication} {@code run} method. * {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader} * and should declare a public constructor that accepts a {@link SpringApplication} * instance and a {@code String[]} of arguments. A new * {@link SpringApplicationRunListener} instance will be created for each run. *
上面兩個接口,ApplicationContextInitializer 在Springboot 啟動前進行必要的屬性初始化的設置抵窒,SpringApplicationRunListener 弛针;監(jiān)聽SpringApplicationrun的方法,監(jiān)聽Spring boot整個生命周期李皇;
下面自定義兩個類削茁,分別實現(xiàn)上面兩個接口
public class JohnSpringListener implements SpringApplicationRunListener {
//這是必須的,否則會報錯
public JohnSpringListener(SpringApplication application, String[] args) {
System.out.println("spring application main class is: " + application.getMainApplicationClass().getName());
System.out.println("spring application main class args: " + args);
}
@Override
public void starting() {
System.out.println(" JohnSpringListener starting");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
System.out.println(" JohnSpringListener environmentPrepared");
String[] defaultProfiles = environment.getDefaultProfiles();
for (String s : defaultProfiles){
System.out.println(s);
}
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println(" JohnSpringListener contextPrepared");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println(" JohnSpringListener contextLoaded");
}
@Override
public void started(ConfigurableApplicationContext context) {
System.out.println(" JohnSpringListener started");
}
@Override
public void running(ConfigurableApplicationContext context) {
System.out.println(" JohnSpringListener running");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println(" JohnSpringListener failed");
}
}
@Component
public class MyApplicationContextInitializer implements ApplicationContextInitializer {
@Autowired
private Environment env;
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
System.out.println("MyApplicationContextInitializer init()");
String[] activeProfiles = configurableApplicationContext.getEnvironment().getActiveProfiles();
}
}
執(zhí)行結(jié)果如下
MyApplicationContextInitializer init()
JohnSpringListener contextPrepared
2020-03-28 11:35:39.849 INFO 22979 --- [ main] c.j.s.SpringBootMybatisApplication : Starting SpringBootMybatisApplication on wenweideMacBook-Air.local with PID 22979 (/Users/wenwei/impoveMent/sourcecode/spring-boot-mybatis/target/classes started by wenwei in /Users/wenwei/impoveMent/sourcecode/springboot-demo)
2020-03-28 11:35:39.854 INFO 22979 --- [ main] c.j.s.SpringBootMybatisApplication : No active profile set, falling back to default profiles: default
JohnSpringListener contextLoaded
2020-03-28 11:35:41.041 INFO 22979 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2020-03-28 11:35:41.043 INFO 22979 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2020-03-28 11:35:41.153 INFO 22979 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 90ms. Found 1 Redis repository interfaces.
2020-03-28 11:35:42.255 INFO 22979 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-03-28 11:35:42.272 INFO 22979 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-03-28 11:35:42.273 INFO 22979 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.31]
2020-03-28 11:35:42.443 INFO 22979 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-03-28 11:35:42.444 INFO 22979 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2418 ms
2020-03-28 11:35:43.816 INFO 22979 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-03-28 11:35:44.640 INFO 22979 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-03-28 11:35:44.644 INFO 22979 --- [ main] c.j.s.SpringBootMybatisApplication : Started SpringBootMybatisApplication in 5.504 seconds (JVM running for 6.428)
JohnSpringListener started
JohnSpringListener running
- 注意 JohnSpringListener 必須實現(xiàn)構(gòu)造器的方法,參數(shù)為springApplication茧跋,args慰丛,
Springboot Spring 容器是如何啟動的呢
在了解Spring boot 中Spring容器啟動 之前,我們先看以下代碼
@Component
public class InvalidBean {
@Autowired
private Environment environment;
private static final Logger LOG = Logger.getLogger(InvalidBean.class);
public InvalidBean() {
LOG.info("***" + Arrays.asList(environment.getDefaultProfiles()));
}
}
在程序啟動之后瘾杭, 收到一行報錯
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.john.springbootmybatis.config.InvalidBean]: Constructor threw exception; nested exception is java.lang.NullPointerException
報錯顯示空指針異常诅病,當構(gòu)造器方法調(diào)用之前,Springbean 還未初始化富寿,導致的空指針異常睬隶;從報錯的堆棧來看,程序應該在刷新refreshContext時候報錯页徐;
如果在代碼中增加init方法苏潜,添加@PostConstruct注解;
@Component
public class InvalidBean {
@Autowired
private Environment environment;
private static final Logger LOG = Logger.getLogger(InvalidBean.class);
@PostConstruct
public void init() {
LOG.info("***" + Arrays.asList(environment.getDefaultProfiles()));
}
}
[圖片上傳失敗...(image-2eb87d-1585377234796)]
Spring 容器的啟動
我們首先定義一個類去實現(xiàn)Spring中生命周期的接口变勇,分別是InitializingBean 恤左,BeanNameAware,BeanFactoryAware,ApplicationContextAware
@Component
public class InitBean implements InitializingBean, BeanFactoryAware, BeanNameAware, ApplicationContextAware,Destroyable {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("init bean: afterPropertiesSet ");
}
@PostConstruct
public void initConstructor(){
System.out.println("InitBean PostConstruct");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
boolean containsBean = beanFactory.containsBean("accountService");
System.out.println("beanFactory.containsBean(accountService) : "+containsBean);
System.out.println("init bean: setBeanFactory ");
}
@Override
public void setBeanName(String s) {
System.out.println("beanName: "+s);
System.out.println("init bean: setBeanName ");
}
@Override
public void destroy() throws DestroyFailedException {
System.out.println("init bean: destroy ");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
AccountMappper bean = applicationContext.getBean(AccountMappper.class);
bean.countUser();
System.out.println("init bean,setApplicationContext");
}
public void init(){
System.out.println("init bean , customInit ");
}
}
可以清楚的看到會按照Spring生命周期的順序,先構(gòu)造初始化bean,然后在執(zhí)行bean中的實現(xiàn)了BeanNameAware搀绣,BeanFactoryAware飞袋,@PostContractor注解,最后執(zhí)行InitializingBean链患;代碼打印結(jié)果如下
beanName: initBean
init bean: setBeanName
beanFactory.containsBean(accountService) : true
init bean: setBeanFactory
2020-03-28 12:03:59.892 INFO 37823 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2020-03-28 12:04:00.514 INFO 37823 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
init bean,setApplicationContext
InitBean PostConstruct
init bean: afterPropertiesSet
init bean , customInit
熟悉Spring 容器的同學會很清楚巧鸭,還有beanpostprocessor,會在Spring 那一段代碼中執(zhí)行呢麻捻?增加以下代碼
@Component
public class JohnBeanPostProccess implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if ("initBean".equalsIgnoreCase(beanName)){
System.out.println("initBean, JohnBeanPostProccess");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("initBean".equalsIgnoreCase(beanName)){
System.out.println("initBean, postProcessAfterInitialization");
}
return bean;
}
}
代碼打印如下,setApplicationContext執(zhí)行后纲仍,會執(zhí)行beanPostprocessor,然后在執(zhí)行@PostConstructor贸毕,執(zhí)行initBean接口郑叠,在執(zhí)行自定義的customInit
init bean,setApplicationContext
initBean, JohnBeanPostProccess
InitBean PostConstruct
init bean: afterPropertiesSet
init bean , customInit
initBean, postProcessAfterInitialization
到此,了解了Spring的生命周期的相關注解的啟動順序明棍;
Spring boot run的源碼
通過上面的代碼展示乡革,初步了解在Springboot 啟動大致流程,下面通過源碼摊腋,對Springboot啟動執(zhí)行過程沸版,有一個更加深入的了解,通過堆棧追蹤兴蒸,能夠看到中SpringApplication.run(SpringBootMybatisApplication.class, args) 執(zhí)行其實分為兩個部分new SpringApplication(primarySources).run(args);
- new SpringApplication的初始化
- run的執(zhí)行推穷;
SpringApplication 初始化源代碼
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判斷webApplication類型,sevelet,reactive,none
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//設置啟動器类咧,即上面自定義以及Springboot默認的啟動器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//設置Spring的監(jiān)聽器馒铃,包括上述自定義的監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
run執(zhí)行的一段源代碼
public ConfigurableApplicationContext run(String... args) {
//
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
總結(jié)
Springboot 通過約定優(yōu)于配置的方式蟹腾,幫助開發(fā)者將默認代碼配置(假定合理的值)設置好,能夠減少開發(fā)者不必要的樣板式的代碼区宇;同時利用起步依賴娃殖,定義與好其它庫的依賴,將具備某種功能包打包整合在一個starter里面议谷,減少不必要的沖突和配置炉爆;通過Spring boot的啟動過程的學習,初步了解Springboot的啟動過程卧晓,以及對Spring boot的一些屬性的設置芬首,有了一個更加深入的了解;