日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

springboot 啟動原理

springboot 常見的啟動寫法如下:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

然后我們的程序就可以和 main 方法一樣,直接啟動運行了。

但是這一切是如何實現(xiàn)的呢?

今天我們一起來學習一下 springboot 的啟動原理。

SpringApplication.run 方法

main 方法整體看起來看起來平平無奇。

平平無奇

SpringApplication.run() 讓我意識到問題并不簡單,我們一起看一下 run 里面到底是如何實現(xiàn)的。

public static ConfigurableApplicationContext run(Object source, String... args) {
    return run(new Object[] { source }, args);
}

這里調用了另外一個方法:

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
}

這里實際上是創(chuàng)建了 SpringApplication 對象,并且執(zhí)行 run 方法。

SpringApplication

我們簡單看一下這個對象。

public SpringApplication(Object... sources) {
    initialize(sources);
}

這里主要是針對 spring 的初始化:

private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    this.webEnvironment = deduceWebEnvironment();
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

設置了一些初始化實現(xiàn)、監(jiān)聽器等,此處不做詳細展開。

run 方法

構建完成之后,需要調用對應的 run 方法,這個方法是比較復雜的,不過也不用太緊張,有興趣的可以深入研究一下。

/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a JAVA main method)
 * @return a running {@link ApplicationContext}
 */
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        analyzers = new FailureAnalyzers(context);
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, analyzers, ex);
        throw new IllegalStateException(ex);
    }
}

我們這里大概梳理一下啟動過程的步驟:

1. 初始化監(jiān)聽器,以及添加到SpringApplication的自定義監(jiān)聽器。

2. 發(fā)布ApplicationStartedEvent事件,如果想監(jiān)聽ApplicationStartedEvent事件,你可以這樣定義:public class ApplicationStartedListener implements ApplicationListener,然后通過SpringApplication.addListener(..)添加進去即可。

3. 裝配參數(shù)和環(huán)境,確定是web環(huán)境還是非web環(huán)境。

4. 裝配完環(huán)境后,就觸發(fā)ApplicationEnvironmentPreparedEvent事件。

5. 如果SpringApplication的showBanner屬性被設置為true,則打印啟動的Banner。

6. 創(chuàng)建ApplicationContext,會根據(jù)是否是web環(huán)境,來決定創(chuàng)建什么類型的ApplicationContext。

7. 裝配Context的環(huán)境變量,注冊Initializers、beanNameGenerator等。

8. 發(fā)布ApplicationPreparedEvent事件。

9. 注冊springApplicationArguments、springBootBanner,加載資源等

10. 遍歷調用所有SpringApplicationRunListener的contextLoaded()方法。

11. 調用ApplicationContext的refresh()方法,裝配context beanfactory等非常重要的核心組件。

12. 查找當前ApplicationContext中是否注冊有CommandLineRunner,如果有,則遍歷執(zhí)行它們。

13. 發(fā)布ApplicationReadyEvent事件,啟動完畢,表示服務已經可以開始正常提供服務了。通常我們這里會監(jiān)聽這個事件來打印一些監(jiān)控性質的日志,表示應用正常啟動了。
面試官:知道 springboot 的啟動原理嗎?

 

啟動流程

@SpringBootApplication 注解

看完了靜態(tài)方法,我們來看一下另一個注解 @SpringBootApplication。

注解定義

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    // 省略方法
}

我們省略掉對應的方法屬性,發(fā)現(xiàn)實際上這個注解是由 3 個注解組合而成:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

其中 @SpringbootConfiguration 是完全等價于 @Configuration 的,此處應該是為了和 spring 的注解做區(qū)分。

所以一開始的實現(xiàn),等價于:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

當然了, springboot 的理念就是極簡配置,能少寫一行代碼,就少寫一行代碼!

@Configuration 注解

這里的 @Configuration 大家應該并不陌生,spring 中可以使用下面的寫法,替代 spring xml 的配置寫法:

@Configuration
public class MockConfiguration{
    @Bean
    public MockService mockService(){
        return new MockServiceImpl(dependencyService());
    }

    @Bean
    public DependencyService dependencyService(){
        return new DependencyServiceImpl();
    }
}

@ComponentScan 注解

@ComponentScan 的功能其實就是自動掃描并加載符合條件的組件(比如 @Component 和 @Service等)或者bean定義,最終將這些bean定義加載到IoC容器中。

我們可以通過basePackages等屬性來細粒度的定制@ComponentScan自動掃描的范圍,如果不指定,則默認Spring框架實現(xiàn)會從聲明 @ComponentScan 所在類的package進行掃描。

ps: 所以我們的 Application 啟動類一般是放在根目錄,這樣連掃描的包也省略掉了。

@EnableAutoConfiguration 注解

這個注解我們放在最后講解,因為它為 springboot 帶來了更多的便利性。

注解定義

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

這個注解實際上是一個組合注解 @AutoConfigurationPackage + @Import

@AutoConfigurationPackage:自動配置包

@Import: 導入自動配置的組件

我們來看一下這 2 個注解:

@AutoConfigurationPackage 注解

這個注解主要是通過 @Import 注解導入了 AutoConfigurationPackages.Registrar.class 類。

實現(xiàn)如下:

/**
 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
 * configuration.
 * @author 老馬嘯西風
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }
    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.<Object>singleton(new PackageImport(metadata));
    }
}

它其實是注冊了一個Bean的定義。

new PackageImport(metadata).getPackageName(),它其實返回了當前主程序類的同級以及子級的包組件。

@Import(EnableAutoConfigurationImportSelector.class)

我們來看一下另外一個注解,@Import(EnableAutoConfigurationImportSelector.class)。

EnableAutoConfigurationImportSelector 實現(xiàn)如下:

public class EnableAutoConfigurationImportSelector
        extends AutoConfigurationImportSelector {

    @Override
    protected boolean isEnabled(AnnotationMetadata metadata) {
        if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
            return getEnvironment().getProperty(
                    EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
                    true);
        }
        return true;
    }

}

這個方法一眼看上去也是平平無奇,因為核心實現(xiàn)都在父類中。

最核心的方法如下:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 不啟用,直接返回無導入
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    try {
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AnnotationAttributes attributes = getAttributes(annotationMetadata);

        // 這一行回去加載 springboot 指定的文件
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        configurations = removeDuplicates(configurations);
        configurations = sort(configurations, autoConfigurationMetadata);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return configurations.toArray(new String[configurations.size()]);
    }
    catch (IOException ex) {
        throw new IllegalStateException(ex);
    }
}

我們用過的各種 springboot-starter,使用起來引入一個 jar 就可以使用了。

都要歸功于下面這個方法:

// 這一行回去加載 springboot 指定的文件
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

這里實際上回去解析一個文件:

Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");

這也就是我們在開發(fā)自己的 springboot-starter 時,為什么需要把自己的啟動類放在 META-INF/spring.factories 文件中的原因,這樣就可以被 springboot 加載,并且生效了。

推薦閱讀:

Spring Boot-11-自定義 springboot starter

小結

到這里,springboot 的啟動原理就講解的差不多了。

springboot 和以前的 spring xml 配置相比較,確實簡化了太多太多。

讓我們可以更加快速,正確的啟動一個 java web 程序。

未來的發(fā)展歷程也必然是這樣,誰更加簡單便捷,誰能提升效率,就是誰的天下。這就是老馬的效率第一定律。

希望本文對你有幫助,如果有其他想法的話,也可以評論區(qū)和大家分享哦。

各位極客的點贊收藏轉發(fā),是老馬持續(xù)寫作的最大動力!

期待與你的下次重逢。

分享到:
標簽:springboot
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定