spring boot 啟動相關的學習

在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的一些屬性的設置芬首,有了一個更加深入的了解;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逼裆,一起剝皮案震驚了整個濱河市郁稍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胜宇,老刑警劉巖耀怜,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異桐愉,居然都是意外死亡财破,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門从诲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來左痢,“玉大人,你說我怎么就攤上這事系洛】⌒裕” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵碎罚,是天一觀的道長磅废。 經(jīng)常有香客問我纳像,道長荆烈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任竟趾,我火速辦了婚禮憔购,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岔帽。我一直安慰自己玫鸟,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布犀勒。 她就那樣靜靜地躺著屎飘,像睡著了一般妥曲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钦购,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天檐盟,我揣著相機與錄音,去河邊找鬼押桃。 笑死葵萎,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的唱凯。 我是一名探鬼主播羡忘,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼磕昼!你這毒婦竟也來了卷雕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤掰烟,失蹤者是張志新(化名)和其女友劉穎爽蝴,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纫骑,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡蝎亚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了先馆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片发框。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖煤墙,靈堂內(nèi)的尸體忽然破棺而出梅惯,到底是詐尸還是另有隱情,我是刑警寧澤仿野,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布铣减,位于F島的核電站,受9級特大地震影響脚作,放射性物質(zhì)發(fā)生泄漏葫哗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一球涛、第九天 我趴在偏房一處隱蔽的房頂上張望劣针。 院中可真熱鬧,春花似錦亿扁、人聲如沸捺典。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽襟己。三九已至引谜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間擎浴,已是汗流浹背煌张。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留退客,地道東北人骏融。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像萌狂,于是被迫代替她去往敵國和親档玻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容