spring-boot-2.0.3不一樣系列之源碼篇

來源:互聯網
上載者:User
前言

  上篇:spring-boot-2.0.3不一樣系列之shiro - 搭建篇,實現了spring-boot與shiro的整合,效果大家也看到了,工程確實整合了shiro的認證與授權功能。如果大家能正確搭建起來,並達到了認證和授權的效果,那說明我們會用了,說明我們知其然了;很好,能滿足工作中的基本要求了。

  但是這樣就夠了嗎?很顯然還是不夠的,知其然而不知其所以然是一道瓶頸,如果我們能跨過這道瓶頸,後面的路會越來越坦蕩。就拿上篇部落格來講,我們僅僅只是在ShiroConfig類中加入了幾個bean配置,怎麼就讓spring-boot整合了shiro,shiro又是如何做到認證和授權的,等等一些列問題,如果我們去細想的話,真的有很多疑點需要我們去探索。

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

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

SpringApplication類

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

  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

    說的內容大概意思如下:

    SpringApplication用於從java main方法引導和啟動Spring應用程式,預設情況下,將執行以下步驟來引導我們的應用程式:

      1、建立一個恰當的ApplicationContext執行個體(取決於類路徑)

      2、註冊CommandLinePropertySource,將命令列參數公開為Spring屬性

      3、重新整理應用程式上下文,載入所有單例bean

      4、觸發全部CommandLineRunner bean

    大多數情況下,像SpringApplication.run(ShiroApplication.class, args);這樣啟動我們的應用,也可以在運行之前建立和自訂SpringApplication執行個體,具體可以參考注釋中樣本。

    SpringApplication可以從各種不同的源讀取bean。 通常建議使用單個@Configuration類來引導,但是我們也可以通過以下方式來設定資源:

      1、通過AnnotatedBeanDefinitionReader載入完全限定類名

      2、通過XmlBeanDefinitionReader載入XML資源位置,或者是通過GroovyBeanDefinitionReader載入groovy指令碼位置

      3、通過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

      從源碼我們看出主要做了三件事:

        1、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())做的事。

        2、createSpringFactoriesInstances,建立指定類型的工廠執行個體

          根據上面擷取的指定類型的工廠名稱列表來執行個體化工廠bean,我們可以簡單的認為通過反射來執行個體化,但是具體的實現也沒那麼簡單,感興趣的小夥伴可以自己去跟。

        3、對工廠執行個體進行排序,然後返回排序後的執行個體列表

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

      deduceMainApplicationClass

        從當前堆疊追蹤列表中擷取main方法所在的類名   

    構造方法總結

      1、 構造自身執行個體

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

      3、 設定屬性List<ApplicationContextInitializer<?>> initializers和List<ApplicationListener<?>> listeners

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

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

      構造方法完成之後,執行個體的屬性值如下

感想

  本來是想著springboot啟動源碼解析只用兩篇來說明的,之後講shiro的源碼;可我寫著寫著發現好多執行個體莫名奇妙的就被執行個體化了,很多細節沒有讀到,所以決定細摳,將springboot的啟動過程拆分成多篇來講解,真真正正的明白springboot在啟動的過程都做了些什麼。

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

參考

  spring boot 2.0 源碼分析(一)

  springboot源碼

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.