在Spring boot 开发中,经常遇到一些注解,类方法;参数以及启动参数,先后顺序以及执行的方式,有点分不清;这篇文章介绍一下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相关的参数设置;同时可以通过ordered进行优先级的设置;下面附带源码介绍
//主要是在Spring context启动之前,注册property sources文件,设置 environment相关的参数设置;同时可以通过ordered进行优先级的设置;下面附带源码介绍 * 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 监听Spring application run方法,可以通过SpringFactoriesLoader 加载相关的资源;而且必须声明公共的构造器来接受Springapplication 以及args 参数;
//监听Spring application run方法,可以通过SpringFactoriesLoader 加载相关的资源;而且必须声明公共的构造器来接受Springapplication 以及args 参数; * 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 ;监听SpringApplicationrun的方法,监听Spring boot整个生命周期;
下面自定义两个类,分别实现上面两个接口
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();
}
}
执行结果如下
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 必须实现构造器的方法,参数为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
报错显示空指针异常,当构造器方法调用之前,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 容器的启动
我们首先定义一个类去实现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生命周期的顺序,先构造初始化bean,然后在执行bean中的实现了BeanNameAware,BeanFactoryAware,@PostContractor注解,最后执行InitializingBean;代码打印结果如下
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 那一段代码中执行呢?增加以下代码
@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执行后,会执行beanPostprocessor,然后在执行@PostConstructor,执行initBean接口,在执行自定义的customInit
init bean,setApplicationContext
initBean, JohnBeanPostProccess
InitBean PostConstruct
init bean: afterPropertiesSet
init bean , customInit
initBean, postProcessAfterInitialization
到此,了解了Spring的生命周期的相关注解的启动顺序;
Spring boot run的源码
通过上面的代码展示,初步了解在Springboot 启动大致流程,下面通过源码,对Springboot启动执行过程,有一个更加深入的了解,通过堆栈追踪,能够看到中SpringApplication.run(SpringBootMybatisApplication.class, args) 执行其实分为两个部分new SpringApplication(primarySources).run(args);
- new SpringApplication的初始化
- run的执行;
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的监听器,包括上述自定义的监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
run执行的一段源代码
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;
}
总结
Springboot 通过约定优于配置的方式,帮助开发者将默认代码配置(假定合理的值)设置好,能够减少开发者不必要的样板式的代码;同时利用起步依赖,定义与好其它库的依赖,将具备某种功能包打包整合在一个starter里面,减少不必要的冲突和配置;通过Spring boot的启动过程的学习,初步了解Springboot的启动过程,以及对Spring boot的一些属性的设置,有了一个更加深入的了解;