spring-boot-2.0.3啓動源碼篇一 - SpringApplication構造方法

前言

  spring-boot-2.0.3應用篇 - shiro集成,實現了spring-boot與shiro的整合,效果你們也看到了,工程確實集成了shiro的認證與受權功能。若是你們能正確搭建起來,並達到了認證和受權的效果,那說明咱們會用了,說明咱們知其然了;很好,能知足工做中的基本要求了。html

  可是這樣就夠了嗎?很顯然仍是不夠的,知其然而不知其因此然是一道瓶頸,若是咱們能跨過這道瓶頸,後面的路會愈來愈坦蕩。就拿上篇博客來說,咱們僅僅只是在ShiroConfig類中加入了幾個bean配置,怎麼就讓spring-boot集成了shiro,shiro又是如何作到認證和受權的,等等一些列問題,若是咱們去細想的話,真的有不少疑點須要咱們去探索。java

  既然咱們要去探索,勢必就要讀源碼了。源碼確實很差讀,在咱們工做當中,當咱們讀同事(或者前同事)寫的代碼的時候,總有那麼一句話:草泥馬,這是哪一個sb寫的,縈繞在咱們的心頭,甚至有時候會發現,這他麼是我本身寫的啊,哎,我操!有時候讀本身寫的代碼都頭疼,更別說看別人寫的了。react

  說了那麼多,咱們切入到正題,接下來會有一系列的文章來解析springboot的啓動過程,而今天咱們只看SpringApplication類的構造方法。web

SpringApplication類

  入口仍是那個熟悉的入口:main函數spring

  SpringApplication類註釋

/**
 * Class that can be used to bootstrap and launch a Spring application from a Java main
 * method. By default class will perform the following steps to bootstrap your
 * application:
 *
 * <ul>
 * <li>Create an appropriate {@link ApplicationContext} instance (depending on your
 * classpath)</li>
 * <li>Register a {@link CommandLinePropertySource} to expose command line arguments as
 * Spring properties</li>
 * <li>Refresh the application context, loading all singleton beans</li>
 * <li>Trigger any {@link CommandLineRunner} beans</li>
 * </ul>
 *
 * In most circumstances the static {@link #run(Class, String[])} method can be called
 * directly from your {@literal main} method to bootstrap your application:
 *
 * <pre class="code">
 * &#064;Configuration
 * &#064;EnableAutoConfiguration
 * public class MyApplication  {
 *
 *   // ... Bean definitions
 *
 *   public static void main(String[] args) throws Exception {
 *     SpringApplication.run(MyApplication.class, args);
 *   }
 * }
 * </pre>
 *
 * <p>
 * For more advanced configuration a {@link SpringApplication} instance can be created and
 * customized before being run:
 *
 * <pre class="code">
 * public static void main(String[] args) throws Exception {
 *   SpringApplication application = new SpringApplication(MyApplication.class);
 *   // ... customize application settings here
 *   application.run(args)
 * }
 * </pre>
 *
 * {@link SpringApplication}s can read beans from a variety of different sources. It is
 * generally recommended that a single {@code @Configuration} class is used to bootstrap
 * your application, however, you may also set {@link #getSources() sources} from:
 * <ul>
 * <li>The fully qualified class name to be loaded by
 * {@link AnnotatedBeanDefinitionReader}</li>
 * <li>The location of an XML resource to be loaded by {@link XmlBeanDefinitionReader}, or
 * a groovy script to be loaded by {@link GroovyBeanDefinitionReader}</li>
 * <li>The name of a package to be scanned by {@link ClassPathBeanDefinitionScanner}</li>
 * </ul>
 *
 * Configuration properties are also bound to the {@link SpringApplication}. This makes it
 * possible to set {@link SpringApplication} properties dynamically, like additional
 * sources ("spring.main.sources" - a CSV list) the flag to indicate a web environment
 * ("spring.main.web-application-type=none") or the flag to switch off the banner
 * ("spring.main.banner-mode=off").
 */
View Code

    說的內容大概意思以下:bootstrap

    SpringApplication用於從java main方法引導和啓動Spring應用程序,默認狀況下,將執行如下步驟來引導咱們的應用程序:緩存

      一、建立一個恰當的ApplicationContext實例(取決於類路徑)springboot

      二、註冊CommandLinePropertySource,將命令行參數公開爲Spring屬性app

      三、刷新應用程序上下文,加載全部單例beanide

      四、觸發所有CommandLineRunner bean

    大多數狀況下,像SpringApplication.run(ShiroApplication.class, args);這樣啓動咱們的應用,也能夠在運行以前建立和自定義SpringApplication實例,具體能夠參考註釋中示例。

    SpringApplication能夠從各類不一樣的源讀取bean。 一般建議使用單個@Configuration類來引導,可是咱們也能夠經過如下方式來設置資源:

      一、經過AnnotatedBeanDefinitionReader加載徹底限定類名

      二、經過XmlBeanDefinitionReader加載XML資源位置,或者是經過GroovyBeanDefinitionReader加載groovy腳本位置

      三、經過ClassPathBeanDefinitionScanner掃描包名稱

    也就是說SpringApplication仍是作了很多事的,具體實現後續會慢慢講來,今天的主角只是SpringApplication構造方法。

  SpringApplication構造方法

    源代碼以下

/**
     * Create a new {@link SpringApplication} instance. The application context will load
     * beans from the specified primary sources (see {@link SpringApplication class-level}
     * documentation for details. The instance can be customized before calling
     * {@link #run(String...)}.
     * @param resourceLoader the resource loader to use
     * @param primarySources the primary bean sources
     * @see #run(Class, String[])
     * @see #setSources(Set)
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = deduceWebApplicationType();
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

    從註釋上來看,就是說建立一個ShiroApplication實例,應用上下文從特定的資源文件中加載bean。能夠在調用run以前自定義實例。

    從源碼上來看,主要是deduceWebApplicationType();getSpringFactoriesInstances(xxx.class);deduceMainApplicationClass();這三個方法,咱們一個一個來看。

    deduceWebApplicationType

      推斷web應用類型

private WebApplicationType deduceWebApplicationType() {
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

// 判斷給定的類是否可以加載,就是說類路徑下是否存在給定的類
public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
    try {
        forName(className, classLoader);
        return true;
    }
    catch (Throwable ex) {
        // Class or one of its dependencies is not present...
        return false;
    }
}

      若是org.springframework.web.reactive.DispatcherHandler可以被加載且org.springframework.web.servlet.DispatcherServlet不可以被加載,那麼判定web應用類型是REACTIVE;若是javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext任意一個不能被加載,那麼判定web應用類型是NONE;若是不能判定是REACTIVE和NONE,那麼就是SERVLET類型;具體這三種類型表明什麼含義,你們能夠查看WebApplicationType中的說明。  

    getSpringFactoriesInstances

      從字面意思看就是獲取spring工廠實例,至於從哪獲取哪些工廠實例,咱們往下看。

      getSpringFactoriesInstances源碼

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // 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;
}
View Code

      從源碼咱們看出主要作了三件事:

        一、loadFactoryNames,加載指定類型的工廠名稱

          loadSpringFactories

          loadSpringFactories源碼

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 ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));    // classLoader.getResources(FACTORIES_RESOURCE_LOCATION)獲取類路徑下所有的META-INF/spring.factories的URL
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {                                        // 遍歷所有的URL,逐個讀取META-INF/spring.factories中的屬性
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                List<String> factoryClassNames = Arrays.asList(
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                result.addAll((String) entry.getKey(), factoryClassNames);        // 屬性所有放入MultiValueMap<String, String> result中,注意result的類型
            }
        }
        cache.put(classLoader, result);                                            // 結果放入緩存,方便下次查找
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}
View Code       

          loadSpringFactories作了如下這些事

            a、  查找類路徑下所有的META-INF/spring.factories的URL

            b、 根據url加載所有的spring.factories中的屬性,spring.factories內容以下

            c、  將全部spring.factories中的值緩存到SpringFactoriesLoader的cache中:

              private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();方便下次調用。

          加載完全部的工廠名稱以後,而後從中獲取指定工廠類型的工廠名稱列表,也就是getOrDefault(factoryClassName, Collections.emptyList())作的事。

        二、createSpringFactoriesInstances,建立指定類型的工廠實例

          根據上面獲取的指定類型的工廠名稱列表來實例化工廠bean,咱們能夠簡單的認爲經過反射來實例化,可是具體的實現也沒那麼簡單,感興趣的小夥伴能夠本身去跟。

        三、對工廠實例進行排序,而後返回排序後的實例列表

          排序規則:@Order從小到大排序,沒有order則按沒排序以前的順序。

      deduceMainApplicationClass

        從當前堆棧跟蹤列表中獲取main方法所在的類名   

    構造方法總結

      一、 構造自身實例

      二、 推測web應用類型,並賦值到屬性webApplicationType

      三、 設置屬性List<ApplicationContextInitializer<?>> initializers和List<ApplicationListener<?>> listeners

        中途讀取了類路徑下全部META-INF/spring.factories的屬性,並緩存到了SpringFactoriesLoader的cache緩存中

      四、 推斷主類,並賦值到屬性mainApplicationClass

      構造方法完成以後,實例的屬性值以下

感想

  原本是想着springboot啓動源碼解析只用兩篇來講明的,以後講shiro的源碼;可我寫着寫着發現好多實例莫名奇妙的就被實例化了,不少細節沒有讀到,因此決定細摳,將springboot的啓動過程拆分紅多篇來說解,真真正正的明白springboot在啓動的過程都作了些什麼。

  補充一句:有時候,不是對手有多強大,只是咱們不敢去嘗試;勇敢踏出第一步,你會發現本身比想象中更優秀!誠如海因斯第一次跑進人類10s大關時所說:上帝啊,原來那扇門是虛掩着的!

參考

  spring boot 2.0 源碼分析(一)

  springboot源碼

相關文章
相關標籤/搜索