1. 程式人生 > >springBoot深入理解--啟動原理

springBoot深入理解--啟動原理

        Spring Boot是為了簡化Spring應用的建立、執行、除錯、部署等而出現的,使用它可以做到專注於Spring應用的開發,而無需過多關注XML的配置。

簡單來說,它提供了一堆依賴打包,並已經按照使用習慣解決了依賴問題---習慣大於約定。

注:Spring Boot預設使用tomcat作為伺服器,使用logback提供日誌記錄。

一:使用前提

       Spring Boot提供了一系列的依賴包,所以需要構建工具的支援:maven 或 gradle。個人僅熟悉maven,所以下面的內容都是maven相關的。

使用

① 新建一個maven專案。

② pom中parent設為 spring-boot-starter-parent

 。建議使用最新的 RELEASE 版本。否則可能需要設定 <repositories/> <pluginRepositories/> 。

③ 新增應用需要的starter模組,作為示例,我們僅新增web starter模組。

  這裡需要解釋下starter模組,簡單的說,就是一系列的依賴包組合。例如web starter模組,就是包含了Spring Boot預定義的一些Web開發的常用依賴:(其中starter模組    Spring Boot應用啟動器基本的一共有44種附文末

○ spring-web, spring-webmvc            Spring WebMvc框架

○ tomcat-embed-*                              內嵌Tomcat容器

○ jackson                                             處理json資料

○ spring-*                                            Spring框架

○ spring-boot-autoconfigure             Spring Boot提供的自動配置功能

換句話說,當你添加了相應的starter模組,就相當於添加了相應的所有必須的依賴包。

至此,pom內容如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.larry.spring</groupId>
    <artifactId>larry-spring-demo4</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>
基本上,如果沒有特別的需要,現在就可以直接寫Controller了!!!--特別的需要 是指設定容器、訪問埠、路徑等。後面再解釋。
package cn.larry.spring.controller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@EnableAutoConfiguration
public class SampleController {

    @RequestMapping("/")
    @ResponseBody
    String home() {
        return "Hello World!";
    }

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

這裡有兩個新東西:@EnableAutoConfiguration 和 SpringApplication 。

二:配置引數詳解

SpringBootApplication背後的祕密

雖然定義使用了多個Annotation進行了原資訊標註,但實際上重要的只有三個Annotation:

  • @Configuration(@SpringBootConfiguration點開檢視發現裡面還是應用了@Configuration)

  • @EnableAutoConfiguration

  • @ComponentScan

所以,如果我們使用如下的SpringBoot啟動類,整個SpringBoot應用依然可以與之前的啟動類功能對等:

每次寫這3個比較累,所以寫一個@SpringBootApplication方便點。接下來分別介紹這3個Annotation。

@Configuration

這裡的@Configuration對我們來說不陌生,它就是JavaConfig形式的Spring Ioc容器的配置類使用的那個@Configuration,SpringBoot社群推薦使用基於JavaConfig的配置形式,所以,這裡的啟動類標註了@Configuration之後,本身其實也是一個IoC容器的配置類。

舉幾個簡單例子回顧下,XML跟config配置方式的區別:

  • 表達形式層面

    基於XML配置的方式是這樣:

而基於JavaConfig的配置方式是這樣:

任何一個標註了@Configuration的Java類定義都是一個JavaConfig配置類。

  • 註冊bean定義層面

    基於XML的配置形式是這樣:

而基於JavaConfig的配置形式是這樣的:

任何一個標註了@Bean的方法,其返回值將作為一個bean定義註冊到Spring的IoC容器,方法名將預設成該bean定義的id。

  • 表達依賴注入關係層面

    為了表達bean與bean之間的依賴關係,在XML形式中一般是這樣:

而基於JavaConfig的配置形式是這樣的:

如果一個bean的定義依賴其他bean,則直接呼叫對應的JavaConfig類中依賴bean的建立方法就可以了。

@ComponentScan

@ComponentScan這個註解在Spring中很重要,它對應XML配置中的元素,@ComponentScan的功能其實就是自動掃描並載入符合條件的元件(比如@Component和@Repository等)或者bean定義,最終將這些bean定義載入到IoC容器中。

我們可以通過basePackages等屬性來細粒度的定製@ComponentScan自動掃描的範圍,如果不指定,則預設Spring框架實現會從宣告@ComponentScan所在類的package進行掃描。

注:所以SpringBoot的啟動類最好是放在root package下,因為預設不指定basePackages。

@EnableAutoConfiguration

個人感覺@EnableAutoConfiguration這個Annotation最為重要,所以放在最後來解讀,大家是否還記得Spring框架提供的各種名字為@Enable開頭的Annotation定義?比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和做事方式其實一脈相承,簡單概括一下就是,藉助@Import的支援,收集和註冊特定場景相關的bean定義。

  • @EnableScheduling是通過@Import將Spring排程框架相關的bean定義都載入到IoC容器。

  • @EnableMBeanExport是通過@Import將JMX相關的bean定義載入到IoC容器。

而@EnableAutoConfiguration也是藉助@Import的幫助,將所有符合自動配置條件的bean定義載入到IoC容器,僅此而已!

@EnableAutoConfiguration作為一個複合Annotation,其自身定義關鍵資訊如下:

其中,最關鍵的要屬@Import(EnableAutoConfigurationImportSelector.class),藉助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應用將所有符合條件的@Configuration配置都載入到當前SpringBoot建立並使用的IoC容器。就像一隻“八爪魚”一樣

藉助於Spring框架原有的一個工具類:SpringFactoriesLoader的支援,@EnableAutoConfiguration可以智慧的自動配置功效才得以大功告成!

自動配置幕後英雄:SpringFactoriesLoader詳解

SpringFactoriesLoader屬於Spring框架私有的一種擴充套件方案,其主要功能就是從指定的配置檔案META-INF/spring.factories載入配置。

配合@EnableAutoConfiguration使用的話,它更多是提供一種配置查詢的功能支援,即根據@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查詢的Key,獲取對應的一組@Configuration類

上圖就是從SpringBoot的autoconfigure依賴包中的META-INF/spring.factories配置檔案中摘錄的一段內容,可以很好地說明問題。

所以,@EnableAutoConfiguration自動配置的魔法騎士就變成了:從classpath中搜尋所有的META-INF/spring.factories配置檔案,並將其中org.springframework.boot.autoconfigure.EnableutoConfiguration對應的配置項通過反射(Java Refletion)例項化為對應的標註了@Configuration的JavaConfig形式的IoC容器配置類,然後彙總為一個並載入到IoC容器。

深入探索SpringApplication執行流程

SpringApplication的run方法的實現是我們本次旅程的主要線路,該方法的主要流程大體可以歸納如下:

1) 如果我們使用的是SpringApplication的靜態run方法,那麼,這個方法裡面首先要建立一個SpringApplication物件例項,然後呼叫這個建立好的SpringApplication的例項方法。在SpringApplication例項初始化的時候,它會提前做幾件事情:

  • 根據classpath裡面是否存在某個特徵類(org.springframework.web.context.ConfigurableWebApplicationContext)來決定是否應該建立一個為Web應用使用的ApplicationContext型別。

  • 使用SpringFactoriesLoader在應用的classpath中查詢並載入所有可用的ApplicationContextInitializer。

  • 使用SpringFactoriesLoader在應用的classpath中查詢並載入所有可用的ApplicationListener。

  • 推斷並設定main方法的定義類。

2) SpringApplication例項初始化完成並且完成設定後,就開始執行run方法的邏輯了,方法執行伊始,首先遍歷執行所有通過SpringFactoriesLoader可以查詢到並載入的SpringApplicationRunListener。呼叫它們的started()方法,告訴這些SpringApplicationRunListener,“嘿,SpringBoot應用要開始執行咯!”。

3) 建立並配置當前Spring Boot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。

4) 遍歷呼叫所有SpringApplicationRunListener的environmentPrepared()的方法,告訴他們:“當前SpringBoot應用使用的Environment準備好了咯!”。

5) 如果SpringApplication的showBanner屬性被設定為true,則列印banner。

6) 根據使用者是否明確設定了applicationContextClass型別以及初始化階段的推斷結果,決定該為當前SpringBoot應用建立什麼型別的ApplicationContext並建立完成,然後根據條件決定是否新增ShutdownHook,決定是否使用自定義的BeanNameGenerator,決定是否使用自定義的ResourceLoader,當然,最重要的,將之前準備好的Environment設定給建立好的ApplicationContext使用。

7) ApplicationContext建立好之後,SpringApplication會再次藉助Spring-FactoriesLoader,查詢並載入classpath中所有可用的ApplicationContext-Initializer,然後遍歷呼叫這些ApplicationContextInitializer的initialize(applicationContext)方法來對已經建立好的ApplicationContext進行進一步的處理。

8) 遍歷呼叫所有SpringApplicationRunListener的contextPrepared()方法。

9) 最核心的一步,將之前通過@EnableAutoConfiguration獲取的所有配置以及其他形式的IoC容器配置載入到已經準備完畢的ApplicationContext。

10) 遍歷呼叫所有SpringApplicationRunListener的contextLoaded()方法。

11) 呼叫ApplicationContext的refresh()方法,完成IoC容器可用的最後一道工序。

12) 查詢當前ApplicationContext中是否註冊有CommandLineRunner,如果有,則遍歷執行它們。

13) 正常情況下,遍歷執行SpringApplicationRunListener的finished()方法、(如果整個過程出現異常,則依然呼叫所有SpringApplicationRunListener的finished()方法,只不過這種情況下會將異常資訊一併傳入處理)

去除事件通知點後,整個流程如下:

SpringApplication 則是用於從main方法啟動Spring應用的類。預設,它會執行以下步驟:

  1. 建立一個合適的ApplicationContext例項 (取決於classpath)。
  2. 註冊一個CommandLinePropertySource,以便將命令列引數作為Spring properties。
  3. 重新整理application context,載入所有單例beans。
  4. 啟用所有CommandLineRunner beans。

預設,直接使用SpringApplication 的靜態方法run()即可。但也可以建立例項,並自行配置需要的設定。

總結

到此,SpringBoot的核心元件完成了基本的解析,綜合來看,大部分都是Spring框架背後的一些概念和實踐方式,SpringBoot只是在這些概念和實踐上對特定的場景事先進行了固化和昇華,而也恰恰是這些固化讓我們開發基於Sping框架的應用更加方便高效。

下面對啟動的提示每一行做一解釋:

⑥ 根據這個資訊,我們可以看出很多東西,不過現在先訪問一下吧。

若想要路徑中加入專案名稱,將專案路徑直接設為根路徑 server.contextPath=/aaa

啟動時的資訊:

第 9 行,啟動SampleController。
第10行,查詢active profile,無,設為default。
第11行,重新整理上下文。
第12行,初始化tomcat,設定埠8080,設定訪問方式為http。
第13行,啟動tomcat服務。
第14行,啟動Servlet引擎。
第15行,Spring內嵌的WebApplicationContext 初始化開始。
第16行,Spring內嵌的WebApplicationContext 初始化完成。
第17行,對映servlet,將 dispatcherServlet 對映到 [/] 。 
第18行,對映filter,將 characterEncodingFilter 對映到 [/*] 。 
第19行,對映filter,將 hiddenHttpMethodFilter 對映到 [/*] 。 
第20行,對映filter,將 httpPutFormContentFilter 對映到 [/*] 。 
第21行,對映filter,將 requestContextFilter 對映到 [/*] 。 
第22行,查詢 @ControllerAdvice。 
第23行,對映路徑 "{[/]}" 到 cn.larry.spring.controller.SampleController.home()。 
第24行,對映路徑 "{[/error]}" 到 org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)。 
第25行,對映路徑 "{[/error],produces=[text/html]}" 到 org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)。 
第26行,略。 第27行,略。 第28行,略。 第29行,略。 
第30行,tomcat啟動完畢。 
第31行,SampleController啟動耗費的時間。 
第32行,初始化 dispatcherServlet 。 
第33行,dispatcherServlet 的初始化已啟動。 
第34行,dispatcherServlet 的初始化已完成。 
第35行,收到shutdown關閉請求。 
第36行,關閉AnnotationConfigEmbeddedWebApplicationContext。 
第37行,略。

從上面的啟動資訊中可以明顯看到SpringMVC的載入過程,特別需要注意的是這種預設方式下載入的幾個 filter 。

具體可以見本文末尾最後三個連結。

參考:

相關推薦

springBoot深入理解--啟動原理

        Spring Boot是為了簡化Spring應用的建立、執行、除錯、部署等而出現的,使用它可以做到專注於Spring應用的開發,而無需過多關注XML的配置。 簡單來說,它提供了一堆依賴打包,並已經按照使用習慣解決了依賴問題---習慣大於約定。 注:

深入理解mysqldump原理 --single-transaction --lock-all-tables --master-data

在mysqldump過程中,之前其實一直不是很理解為什麼加了--single-transaction就能保證innodb的資料是完全一致的,而myisam引擎無法保證,必須加--lock-all-tables,前段時間抽空詳細地查看了整個mysqldump過程。 理解master-data和--

記憶體系列二:深入理解硬體原理

本篇文章承接上文繼續介紹DDR記憶體的硬體原理,包括如何定址,時序和時延以及可以為提高記憶體的效能可以有哪些方法。 上次雖然解決了小張的問題,卻引發了他對記憶體原理的興趣。這不他又來找我了,說我還欠他一個解釋。這次我們約在一個咖啡館見面,這次內容有點深入,我帶了些圖片,小張也點了一大杯美式,計劃大幹一

深入理解mybatis原理》 MyBatis快取機制的設計與實現

本文主要講解MyBatis非常棒的快取機制的設計原理,給讀者們介紹一下MyBatis的快取機制的輪廓,然後會分別針對快取機制中的方方面面展開討論。 MyBatis將資料快取設計成兩級結構,分為一級快取、二級快取:       &nb

深入理解mybatis原理》 MyBatis的二級快取的設計原理

       MyBatis的二級快取是Application級別的快取,它可以提高對資料庫查詢的效率,以提高應用的效能。本文將全面分析MyBatis的二級快取的設計原理。 1.MyBatis的快取機制整體設計以及二級快取的工作模式  

深入理解計算機原理——程式與執行(二)

浮點數 (1)       浮點數的表示方法; (2)       浮點數的精度與範圍; (3)       浮點數的分佈; (4)       浮點數階碼的表示方法; (5)       浮點數位數規格化; (6)       結合例子學習浮點數的表示方法; (7)       IEEE 754標準; 2

簡述JAVA GC回收機制,深入理解GC原理

什麼是“GC” 垃圾回收機制。 為什麼要用“GC” 眾所周知,JAVA 這語言,與C語言不同,Java記憶體(堆記憶體)的分配與回收由JVM垃圾收集器自動完成,比如 C語言自己定義的變數,不用時需要 自己回收這個變數 。JAVA 這就是自動完成了,自動檢測,無用的

深入理解PHP原理之Opcodes

最近要給Yahoo的同事們做一個關於PHP和Apache處理請求的內部機制的講座,剛好寫了些關於Opcodes的文字,就發上來了,這個文章基於 Sara Golemon大師的《Understanding OPcode》 Opcode是一種PHP指令碼編譯後的中間語言,就像Java的Byt

深入理解Memcached原理

1.為什麼要使用memcache  由於網站的高併發讀寫需求,傳統的關係型資料庫開始出現瓶頸,例如: 1)對資料庫的高併發讀寫: 關係型資料庫本身就是個龐然大物,處理過程非常耗時(如解析SQL語句,事務處理等)。如果對關係型資料庫進行高併發讀寫(每秒上萬次的訪問),那麼它是

深入理解mybatis原理(三)》 MyBatis的一級快取實現詳解 及使用注意事項

0.寫在前面         MyBatis是一個簡單,小巧但功能非常強大的ORM開源框架,它的功能強大也體現在它的快取機制上。MyBatis提供了一級快取、二級快取 這兩個快取機制,能夠很好地處理和維護快取,以提高系統的效能。本文的目的則是向讀者詳細介紹MyBatis的一級快取,深入原始碼,解析MyBa

深入理解PHP原理之函式(Introspecting PHP Function)

 宣告:此內容轉載鳥哥,只是留作個人學習、記錄用。為表尊重,特此說明。 在PHP中,函式分為倆種, •一種是zend_internal_function, 這種函式是由擴充套件或者Zend/PHP核心提供的,用’C/C++’編寫的,可以直接執行的函式。 •另外一種是ze

深入理解作業系統原理之檔案系統

一、概述 作業系統對系統的軟體資源(不論是應用軟體和系統軟體)的管理都以檔案方式進行,承擔這部分功能的作業系統稱為檔案系統。 1、檔案 計算機系統對系統中軟體資源:無論是程式或資料、系統軟體或應用軟體都以檔案方式來管理。檔案是存貯在某種介質上的(如磁碟

SpringBoot學習筆記一之【Idea下建立springboot示例、啟動原理分析與兩種部署啟動方式】

1、使用背景 首先說下我們為什麼使用springboot,原因有以下幾點 1、快速建立獨立執行的spring專案以及與主流框架繼承 2、使用嵌入式的Servlet容器,無需打成war包 3、starters自動依賴於版本控制 4、大量的自動配置,簡化開發,也可修改預設值 5、

深入理解mybatis原理》 Mybatis初始化機制詳解 侵立刪

對於任何框架而言,在使用前都要進行一系列的初始化,MyBatis也不例外。本章將通過以下幾點詳細介紹MyBatis的初始化過程。   1.MyBatis的初始化做了什麼   2. MyBatis基於XML配置檔案建立Configuration物件的過程 &nb

深入理解作業系統原理之程序管理(一)

一、概述 1、為什麼引入程序 程式併發執行時具有如下特徵: 間斷性 程式在併發執行時,由於它們共享資源或為完成同一項任務而相互合作,使在併發程式之間形成了相互制約的關係。相互制約將導致併發程式具有“執行-暫停-執行”這種間斷性活動規律。 失去封閉性

深入理解PHP原理之變數 【轉自(風雪之隅)】

PHP作為一個弱型別,動態的指令碼語言(不嚴格驗證變數型別)。宣告一個變數的時候,並不需要顯示指定它儲存的資料型別。<?php$var = 1; //int$var = "goith"; //st

使用WireShark深入理解HTTPS原理

前端開發,移動開發,後臺開發,都應該知道HTTP通訊,而且現在一些大廠都已經使用HTTPS進行通訊,那HTTPS到底是什麼呢,有什麼好處呢? 一.為什麼要使用HTTPS http是一種通訊上極為簡便的協議,他本身不帶有安全功能,http包在網路上傳輸的過程中難免會被人

深入理解Memcache原理

1.為什麼要使用memcache  由於網站的高併發讀寫需求,傳統的關係型資料庫開始出現瓶頸,例如: 1)對資料庫的高併發讀寫: 關係型資料庫本身就是個龐然大物,處理過程非常耗時(如解析SQL語句,事務處理等)。如果對關係型資料庫進行高併發讀寫(每秒上萬次的訪問),那麼它是

深入理解PHP原理之--echo的實現

PHP原始碼分析-echo實現詳解 原諒出處:http://jackywdx.cn/2009/01/implement_of_php_echo echo,這個是PHP運用得最多的標記之一,算不上是函式,PHP手冊裡這麼寫的,因為它沒有返回值。今天好奇就去看看PHP的原始碼

深入理解mybatis原理(十二)》 mybatis深入理解之#與$區別

一、介紹 mybatis 中使用 Mapper.xml裡面的配置進行 sql 查詢,經常需要動態傳遞引數,例如我們需要根據使用者的姓名來篩選使用者時,sql 如下: select * from user where name = "Jack";上述 sql 中,我們希