SpringBoot啓動流程

1.SpringBoot項目啓動方式:

  1. 在IDE中啓動SpringBoot主類(XXXApplication)中的main方法
  2. 使用mvn spring-boot:run命令啓動
  3. 打成jar包以後使用java -jar xxx.jar運行
  4. 打成war包以後放在web容器中運行

這是一篇一年多前寫的博客,使用的源碼版本是1.5.x。當時發佈在CSDN,如今同步到其餘平臺,雖然SpringBoot這個版本帝刷的很快,可是2.x版本的啓動流程並無怎麼變化,同樣可供參考。java

2.SpringBoot啓動流程主要分爲三步:

第一部分:SpringApplication初始化模塊,配置一些基本的環境變量,資源,監聽器,構造器;web

第二部分:實現了應用具體的啓動方案,包括流程的監聽模塊,加載配置環境模塊以及建立上下文環境模塊redis

第三部分:自動化配置模塊,這個模塊是實現SpringBoot的自動配置spring


SpringBoot程序的主入口就是標註了@SpringBootApplication註解的類,該類中有一個main方法,在main方法中調用SpringApplication的run()方法,這個run()方法來啓動整個程序segmentfault

@SpringBootApplication
public class CrmWebApiApplication extends SpringBootServletInitializer {

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

下面是@SpringBootApplication註解的頭部源碼mybatis

@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 {

這是一個組合註解,其中標註的註解主要有如下做用app

@EnableAutoConfiguration: 開啓SpringBoot自動配置,在程序啓動時會自動加載SpringBoot的默認配置,若是有對一些參數進行配置,則會在程序啓動時或調用時進行追加或者覆蓋

@SpringBootConfiguration: 這個註解和@Configuration註解的做用同樣,用來表示被標註的類是一個配置類,會將被標註的類中一個或多個被@Bean註解修飾的方法添加到Spring容器中,實例的名字默認是方法名less

@ComponentScan: 包掃描註解,默認掃描主類包路徑下的類spring-boot


進入run()方法後的代碼以下:post

/**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified sources using default settings and user supplied arguments.
     * @param sources the sources to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return new SpringApplication(sources).run(args);
    }

這裏會建立一個SpringApplication類的實例,進入SpringApplication類中能夠看到構造方法裏調用了一個initialize(sources)方法

/**
     * Create a new {@link SpringApplication} instance. The application context will load
     * beans from the specified sources (see {@link SpringApplication class-level}
     * documentation for details. The instance can be customized before calling
     * {@link #run(String...)}.
     * @param sources the bean sources
     * @see #run(Object, String[])
     * @see #SpringApplication(ResourceLoader, Object...)
     */
    public SpringApplication(Object... sources) {
        initialize(sources);
    }

Initialize(sources)方法源碼以下:

@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        //將sources設置到SpringApplication類的source屬性中,這時的source值只有主類
        this.sources.addAll(Arrays.asList(sources));
    }
    //判斷是否是web程序,
    this.webEnvironment = deduceWebEnvironment();
    //從spring.factories文件中找出key爲ApplicationContextInitializer的類進行實例化,而後設置到SpringApplciation類的initializers屬性中,這個過程也是找出全部的應用程序初始化器
    setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));
    //從spring.factories文件中找出key爲ApplicationListener的類並實例化後設置到SpringApplication的listeners屬性中。這個過程就是找出全部的應用程序事件監聽器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //找出main類,也就是SpringBoot項目的主類
    this.mainApplicationClass = deduceMainApplicationClass();
}

2.1 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);
     //加載SpringBoot配置環境ConfigurableEnvironment,見2.2配置ConfigurableEnvironment
     ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
     //打印banner
     Banner printedBanner = printBanner(environment);
     //建立應用程序上下文,見2.3 建立應用程序上下文
     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);
   }
}

2.2 配置ConfigurableEnvironment

加載SpringBoot配置環境ConfigurableEnvironment流程以下:

private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared(environment);
        if (!this.webEnvironment) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        return environment;
    }

在加載配置環境的過程當中會判斷是不是web容器啓動,若是是容器啓動會加載StandardServletEnvironment

private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        if (this.webEnvironment) {
            return new StandardServletEnvironment();
        }
        return new StandardEnvironment();
    }

StandardServletEnvironment類的繼承關係以下,StandardServletEnvironment

StandardServletEnvironment類關係圖.png

PropertyResolver接口是用於解析任何基礎源的屬性的接口,在加載完配置以後會將配置環境加入到監聽器對象SpringApplicationRunListeners中。

2.3 建立應用程序上下文

而後會建立應用上下文對象,具體代碼以下:

protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                contextClass = Class.forName(this.webEnvironment
                        ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, "
                                + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
    }

方法會先顯式的獲取應用上下文對象,若是對象爲空,再加載默認的環境配置,經過是不是webEnvironment進行判斷,默認選擇的是AnnotationConfigApplicationContext(註解上下文,經過掃秒註解來加載bean),而後經過BeanUtils來實例化應用上下文對象而後返回,ConfigurableApplicationContext類繼承關係以下:

這裏推薦一下個人另外一篇博客,不太懂ConfigurableApplicationContext的能夠去看一下,https://juejin.im/post/5d7205...

回到run()方法中,會調用prepareContext()方法將environment, listeners,applicationArguments, printedBanner等組件與上下文對象進行關聯

private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }

        // Add boot specific singleton beans
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }

        // Load the sources
        Set<Object> sources = getSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[sources.size()]));
        listeners.contextLoaded(context);
    }

而後會調用refreshContext()方法,實際調用org.springframework.context.support.AbstractApplicationContext.refresh()內的相關方法。這個方法裏會進行redis,mybatis等的自動配置,包括spring.factories的加載,bean的實例化,BenFactoryPostProcessor接口的執行,BeanPostProcessor接口的執行,條件註解的解析,國際化功能的初始化等。

refreshContext()方法執行完畢以後會執行afterRefresh方法,當run()方法執行完以後Spring容器也就初始化完畢了

protected void afterRefresh(ConfigurableApplicationContext context,
            ApplicationArguments args) {
        callRunners(context, args);
    }

    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<Object>();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        for (Object runner : new LinkedHashSet<Object>(runners)) {
            if (runner instanceof ApplicationRunner) {
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }

    private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
        try {
            (runner).run(args);
        }
        catch (Exception ex) {
            throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
        }
    }

    private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
        try {
            (runner).run(args.getSourceArgs());
        }
        catch (Exception ex) {
            throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
        }
    }

談談理想.jpg

相關文章
相關標籤/搜索