Spring Boot源碼(二) - SpringApplication的run方法

 

目錄

一、構造器初始化和run之前可以調整的配置

1、確定當前ApplicationContext類型

2、使用類加載器加載ApplicationContextInitializer和ApplicationListener

1)、獲取類加載器(默認不會傳入)

2)、通過類加載器和名稱獲取類全限定名

3)、根據類加載器、環境參數等信息反射生成類

4)、排序(按照@Order等)

二、SpringApplication的run方法(主要流程節點)


一、構造器初始化和run之前可以調整的配置

    Spring Boot在執行SpringApplication的run方法之前,其實在構造函數中已經完成了很多初始化的準備操作,並且在run方法之前我們可以配置很多信息(上一篇博客中分析了很多的字段都是有Setter的)。比如我們可以將代碼寫作如下:

public static void main(String[] args) {
    SpringApplication springApplication = new SpringApplication(KevinToolApplication.class);
    springApplication.setWebApplicationType(WebApplicationType.SERVLET);
    springApplication.run(args);
}

    先看看全參構造器本身都初始化了哪些組件或者配置:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 確定當前的ApplicationContext類型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 設置ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 設置ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 確定main方法所在的類
    this.mainApplicationClass = deduceMainApplicationClass();
}

一般情況下類加載器爲null,main方法所在的類則會傳入(比如當前爲KevinToolApplication.class)。

 

1、確定當前ApplicationContext類型

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

    現在是幾個基礎判斷,後續可以通過Settter進行修改,最好根據createApplicationContext方法映射ApplicationContext類型。

1)、如果有DispatcherHandler,沒有DispatcherServlet和ServletContainer則創建REACTIVE類型。

2)、如果沒有ConfigurableWebApplicationContext類型,則創建NONE(非Web類型)。

3)、否則就創建SERVLET普通Web類型。

 

2、使用類加載器加載ApplicationContextInitializerApplicationListener

    都會使用統一的加載方法getSpringFactoriesInstances進行處理:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

1)、獲取類加載器(默認不會傳入)

    這裏可以思考,同一個class文件,不同的類加載器加載完成後會得到兩個類。所以每有特殊情況的話最好不要傳入自己的類加載器,只是Spring Boot在這裏做了擴展點。

public ClassLoader getClassLoader() {
    if (this.resourceLoader != null) {
        return this.resourceLoader.getClassLoader();
    }
    return ClassUtils.getDefaultClassLoader();
}
public static ClassLoader getDefaultClassLoader() {
    ClassLoader cl = null;
    try {
        cl = Thread.currentThread().getContextClassLoader();
    }   // 省略部分代碼
    return cl;
}

      比較情況的看到,或當前線程的類加載器(AppClassLoader類型),保證整個系統中使用同一個類加載器。

 

2)、通過類加載器和名稱獲取類全限定名

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        Enumeration<URL> urls = (classLoader != null ?
                // FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    } // 省略部分代碼
}

    首先在"META-INF/spring.factories"中獲取對於的配置信息,並且按照類加載器進行緩存。緩存對象cache的結構爲:Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

3)、根據類加載器、環境參數等信息反射生成類

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
                                                   ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

    直接通過反射創建對象。

4)、排序(按照@Order等)

    所有使用該公共方法獲取的類型都可以添加@Order或@Priority或Ordered,最後都會使用java.util.Comparator的子類AnnotationAwareOrderComparator進行排序處理。

 

二、SpringApplication的run方法(主要流程節點)

public ConfigurableApplicationContext run(String... args) {
    // 使用Spring自己的定時器工具
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 設置Headless
    configureHeadlessProperty();
    // 獲取SpringApplicationRunListener類型
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // SpringApplicationRunListener生命週期的 starting
    listeners.starting();
    try {
        // 將main方法的args參數進行封裝,爲後續的CommandLineRunner回調做準備
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 準備Environment
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 打印Banner圖,就使用默認的SpringBootBanner即可
        Banner printedBanner = printBanner(environment);
        // 根據SpringApplication構造器出事化的WebApplicationType類型,反射創建ApplicationContext對象
        context = createApplicationContext();
        // 獲取SpringBootExceptionReporter類型
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        // Spring的ApplicationContext的refresh前的準備工作(ApplicationContextInitializer完成初始回調,
        // SpringApplicationRunListener生命週期的 contextLoaded)
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // Spring的ApplicationContext的refresh,ServletWebServerApplicationContext類型則會在onRefresh階段執行
        // createWebServer完成內嵌Servlet容器的創建
        refreshContext(context);
        // refresh完成後的動作,默認爲空留給子類實現
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            // 如果需要打印日誌,則將StopWatch中的啓動信息進行打印
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // SpringApplicationRunListener生命週期的 started
        listeners.started(context);
        // 完成ApplicationRunner和CommandLineRunner的回調(主要用於與args相關的回調)
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        // 異常處理
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }
    
    try {
        // SpringApplicationRunListener生命週期的running
        listeners.running(context);
    }
    catch (Throwable ex) {
        // 異常處理
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

    之前說過AbstractApplicationContext的refresh分爲模板方法,那當前的run方法也爲模板方法,定義了執行流程。那麼,當前主要關注的流程節點有:

1)、SpringApplicationRunListener對應的生命週期,一直貫穿着整個的run方法

2)、prepareEnvironment(準備環境)

    初始化ConfigurableEnvironment類型,並且賦值給ApplicationContext。可以參見:SpringIoc源碼(六)- ApplicationContext(二)- refresh(obtainFreshBeanFactory和StandardEnvironment)

3)、printBanner(打印Banner圖)

4)、createApplicationContext(反射創建ApplicationContext對象)

5)、SpringBootExceptionReporter(異常處理器的獲取)

    爲了以後執行異常時,調用SpringBootExceptionReporter列表的回調方法。

6)、prepareContext(ApplicationContext的refresh前的準備工作)

    ApplicationContext的refresh前的準備工作(ApplicationContextInitializer完成初始回調,SpringApplicationRunListener生命週期的 contextLoaded)

7)、refreshContext(ApplicationContext的refresh執行)

    ApplicationContext的refresh,ServletWebServerApplicationContext類型則會在onRefresh階段執行。createWebServer完成內嵌Servlet服務器(如:Tomcat、Jetty)的創建。

8)、afterRefresh(模板空方法)

    refresh完成後的動作,默認爲空留給子類實現

9)、如果需要打印日誌,則將StopWatch中的啓動信息進行打印

10)、callRunners(main方法args相關回調)

    完成ApplicationRunner和CommandLineRunner的回調(主要用於與args相關的回調)

11)、handleRunFailure(中間有異常出現,異常邏輯回調)

 

詳細主要節點,後續進行分析

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章