spring boot 启动相关的学习

在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的一些属性的设置,有了一个更加深入的了解;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容