spring boot 啟動(dòng)相關(guān)的學(xué)習(xí)

在Spring boot 開(kāi)發(fā)中,經(jīng)常遇到一些注解,類(lèi)方法;參數(shù)以及啟動(dòng)參數(shù),先后順序以及執(zhí)行的方式,有點(diǎn)分不清;這篇文章介紹一下Spring boot的啟動(dòng)邏輯;

maven 依賴(lài)

<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的啟動(dòng)相關(guān)接口

  • ApplicationContextInitializer 主要是在Spring context啟動(dòng)之前,注冊(cè)property sources文件,設(shè)置 environment相關(guān)的參數(shù)設(shè)置;同時(shí)可以通過(guò)ordered進(jìn)行優(yōu)先級(jí)的設(shè)置;下面附帶源碼介紹

    • //主要是在Spring context啟動(dòng)之前,注冊(cè)property sources文件,設(shè)置 environment相關(guān)的參數(shù)設(shè)置;同時(shí)可以通過(guò)ordered進(jìn)行優(yōu)先級(jí)的設(shè)置;下面附帶源碼介紹
      * 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)聽(tīng)Spring application run方法,可以通過(guò)SpringFactoriesLoader 加載相關(guān)的資源;而且必須聲明公共的構(gòu)造器來(lái)接受Springapplication 以及args 參數(shù);

    • //監(jiān)聽(tīng)Spring application run方法,可以通過(guò)SpringFactoriesLoader 加載相關(guān)的資源;而且必須聲明公共的構(gòu)造器來(lái)接受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.
      *
        
      

上面兩個(gè)接口,ApplicationContextInitializer 在Springboot 啟動(dòng)前進(jìn)行必要的屬性初始化的設(shè)置,SpringApplicationRunListener ;監(jiān)聽(tīng)SpringApplicationrun的方法,監(jiān)聽(tīng)Spring boot整個(gè)生命周期;

下面自定義兩個(gè)類(lèi),分別實(shí)現(xiàn)上面兩個(gè)接口

public class JohnSpringListener implements SpringApplicationRunListener {
        //這是必須的,否則會(huì)報(bào)錯(cuò)
    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 必須實(shí)現(xiàn)構(gòu)造器的方法,參數(shù)為springApplication,args,

Springboot Spring 容器是如何啟動(dòng)的呢

在了解Spring boot 中Spring容器啟動(dòng) 之前,我們先看以下代碼

@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()));
    }
}

在程序啟動(dòng)之后, 收到一行報(bào)錯(cuò)

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.john.springbootmybatis.config.InvalidBean]: Constructor threw exception; nested exception is java.lang.NullPointerException

報(bào)錯(cuò)顯示空指針異常,當(dāng)構(gòu)造器方法調(diào)用之前,Springbean 還未初始化,導(dǎo)致的空指針異常;從報(bào)錯(cuò)的堆棧來(lái)看,程序應(yīng)該在刷新refreshContext時(shí)候報(bào)錯(cuò);

如果在代碼中增加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 容器的啟動(dòng)

我們首先定義一個(gè)類(lèi)去實(shí)現(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 ");
    }
}

可以清楚的看到會(huì)按照Spring生命周期的順序,先構(gòu)造初始化bean,然后在執(zhí)行bean中的實(shí)現(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 容器的同學(xué)會(huì)很清楚,還有beanpostprocessor,會(huì)在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í)行后,會(huì)執(zhí)行beanPostprocessor,然后在執(zhí)行@PostConstructor,執(zhí)行initBean接口,在執(zhí)行自定義的customInit

init bean,setApplicationContext
initBean, JohnBeanPostProccess
InitBean PostConstruct
init bean: afterPropertiesSet 
init bean , customInit 
initBean, postProcessAfterInitialization

到此,了解了Spring的生命周期的相關(guān)注解的啟動(dòng)順序;

Spring boot run的源碼

通過(guò)上面的代碼展示,初步了解在Springboot 啟動(dòng)大致流程,下面通過(guò)源碼,對(duì)Springboot啟動(dòng)執(zhí)行過(guò)程,有一個(gè)更加深入的了解,通過(guò)堆棧追蹤,能夠看到中SpringApplication.run(SpringBootMybatisApplication.class, args) 執(zhí)行其實(shí)分為兩個(gè)部分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類(lèi)型,sevelet,reactive,none
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   //設(shè)置啟動(dòng)器,即上面自定義以及Springboot默認(rèn)的啟動(dòng)器
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   //設(shè)置Spring的監(jiān)聽(tīng)器,包括上述自定義的監(jiān)聽(tīng)器
   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 通過(guò)約定優(yōu)于配置的方式,幫助開(kāi)發(fā)者將默認(rèn)代碼配置(假定合理的值)設(shè)置好,能夠減少開(kāi)發(fā)者不必要的樣板式的代碼;同時(shí)利用起步依賴(lài),定義與好其它庫(kù)的依賴(lài),將具備某種功能包打包整合在一個(gè)starter里面,減少不必要的沖突和配置;通過(guò)Spring boot的啟動(dòng)過(guò)程的學(xué)習(xí),初步了解Springboot的啟動(dòng)過(guò)程,以及對(duì)Spring boot的一些屬性的設(shè)置,有了一個(gè)更加深入的了解;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容