悶棍暴打面試官 SpringCloud源碼系列 : (一) SpringCloud的前世今生

單機架構 VS 微服務架構 哪個是21世紀的Web領域的趨勢 ?

答案: 微服務架構是單機架構的未來, 但不是銀彈多用於增長型業務!

如果你是一位軟件行業從業者,尤其是從事服務器端或者後臺系統軟件開發,相信近年來一定被層出不窮的商業名詞所包圍:NoSQL、Big Data、Web-scale、Sharding、Eventual consistency、ACID、CAP理論、雲服務、MapReduce和Real-time等,所有這些其實都圍繞着如何構建高效存儲與數據處理這一核心主題。

過去十年,在數據庫領域與分佈式系統方面涌現了許多引人矚目的進展,由此深刻地影響了如何構建上層應用系統。分析這些激動人心的變化背後,你會發現有以下幾個非常重要的驅動因素:
互聯網公司,包括Google、Yahoo! 、Amazon、Facebook、LinkedIn、Microsoft,以及Twitter等,它們每天都在面對海量數據和負載,迫使其不斷創新,並改進支撐系統以更有效地處理這種量級的數據。

商業方面因素,如敏捷開發、測試驅動和對市場機會做出快速反應等,都要求儘量縮短產品開發週期,因此係統中的數據模型也要足夠靈活以方便調整。

硬件方面,CPU主頻增長日趨緩慢,而多核系統成爲新常態,網絡速度則依舊保持快速發展,這就意味着並行分佈式系統將會成爲業界主流。

<數據密集型應用系統設計>

良心推薦人手一本 -> Designing Data-Intensive Applications

先有SpringCloud 還是先有 微服務 ?

答案: 先有微服務後有SpringCloud !

2014年3月25日 敏捷開發教父 Martin FowlerMicroservices 一文中 對於 Microservice Architecture 進行了條理清晰的論述, 向世人展示了進可攻退可守的微服務架構思想, 奠定了後來者對 微服務的認知.

早期 SpringCloud & Angel 系列基於Spring Boot 1.2.x, 而1.2版本最早誕生於 2014年12月11日,也就是說至少晚了8個月多! 其中很多設計思想也來源於前者!
(數據來源: github) 又一個活生生的學術界驅動工業界的例子. 所以大家有空還是要關注一下 學術界大牛們的新作.才能保證走在技術最前沿.

微服務的定義

筆者通過翻閱 Martin Fowler 發表的文章 Microservices , 將微服務理念梳理爲以下 7 點

  • 通過服務進行組件化: 組件是獨立可替換和可升級的軟件單元。微服務架構將使用庫,但是它們將自己的軟件組成組件的主要方式是分解成服務。我們將庫定義爲鏈接到程序並使用內存中函數調用進行調用的組件,而服務則是進程外組件,它們通過某種機制(例如Web服務請求或遠程過程調用)進行通信。

  • 分散治理: 集中治理的後果之一是傾向於在單一技術平臺上實現標準化。經驗表明,這種方法是束手無策的-並非每個問題都是釘子,也不是每個解決方案都是錘子。我們更喜歡使用正確的工具來完成工作,而整體式應用程序可以在一定程度上利用不同的語言,但這並不常見。

  • 分散數據管理: 數據管理的分散化以多種不同的方式呈現。從最抽象的角度講,這意味着系統的世界概念模型將有所不同。在大型企業中進行集成時,這是一個常見問題,客戶的銷售視圖將與支持視圖不同。在銷售視圖中被稱爲客戶的某些內容可能根本不會出現在支持視圖中。那些具有相同屬性的屬性可能具有不同的語義,並且(更差的)公共屬性具有不同的語義。

  • 智能端點和啞管道: 在不同流程之間建立通信結構時,我們已經看到了許多產品和方法,這些產品和方法強調在通信機制本身中投入大量智慧。一個很好的例子是企業服務總線(ESB),其中ESB產品通常包括用於消息路由,編排,轉換和應用業務規則的複雜工具。

  • 基礎設施自動化: 我們希望儘可能地放心我們的軟件正在運行,因此我們運行了許多自動化測試。升級工作軟件的渠道意味着我們可以自動部署 到每個新環境。

  • 失敗設計: 使用服務作爲組件的結果是,需要對應用程序進行設計,以便它們可以容忍服務故障。由於供應商不可用,任何服務呼叫都可能失敗,客戶必須儘可能優雅地響應此請求。與單片設計相比,這是一個缺點,因爲它引入了額外的處理複雜性。結果是微服務團隊不斷反思服務故障如何影響用戶體驗。Netflix的Simian Army 在工作日內導致服務甚至數據中心發生故障,以測試應用程序的彈性和監視能力。

  • 進化設計: 每當您嘗試將軟件系統分解爲組件時,您都會面臨如何劃分各個部分的決定-我們決定對應用程序進行分割的原則是什麼?組件的關鍵屬性是獨立替換和可升級性的概念[13] -這意味着我們尋找可以想象重寫組件而不影響其協作者的觀點。實際上,許多微服務組通過明確期望許多服務將被廢棄而不是長期發展而將其進一步發展。

    沒接觸過微服務的小夥伴看完暈倒了過去,😥 過一會醒來瘋狂撓頭, 這知識不過腦子啊 !

    輕點,注意髮量哈👴
    其實每一個點對應的都是一種微服務場景的解決方案。 而這些解決方案可能是 Spring 官方提供,有可能是 別的公司提供. 或者兩者都有…

接單成功

微服務之我見

筆者才畢業時那會業內流行 Dubbo , 只要涉及到有點難度的項目 別管併發,數據量怎麼樣, 都一律上 Dubbo 生產者消費者分離, 和今天小夥伴們使用 SpringCloud 的熱情如出一轍, 但是那時候 微服務這個概念並不是很火熱 !

Dubbo 官方定義: Apache Dubbo |ˈdʌbəʊ| 是一款高性能、輕量級的開源Java RPC框架,它提供了三大核心能力:面向接口的遠程方法調用,智能容錯和負載均衡,以及服務自動註冊和發現。

img

SpringCloud 官方定義: SpringCloud基於SpringBoot爲開發人員提供了組件,以快速構建分佈式系統中的一些常見模式(例如,配置中心,服務發現,斷路器,智能路由,微代理,控制總線,一次性令牌,全局鎖,領導選舉,分佈式會話,羣集狀態)。分佈式系統的協調導致樣板式樣,並且使用Spring Cloud開發人員可以快速站起來實現這些樣板的服務和應用程序。它們可以在任何分佈式環境中正常工作,包括開發人員自己的筆記本電腦,裸機數據中心以及Cloud Foundry等託管平臺。

Diagram

那麼 Dubbo 和 SpringCloud 有什麼區別呢?

從技術棧上來看

dubbo:zookeeper+dubbo+springmvc/springboot
通信方式:rpc
註冊中心:zookeeper,nacos
配置中心:diamond(淘寶開發)

spring cloud:spring+Netflix
通信方式:http restful
註冊中心:eureka,consul,nacos				
配置中心:config
斷路器:hystrix
網關:zuul,gateway
分佈式追蹤系統:sleuth+zipkin

VS

誠然 Dubbo 已經跟不上目前 微服務思想的發展了, 我們在做微服務的時候 首選 SpringCloud.那 SpringCloud 有那麼多組合我們選哪個好呢?

SpringCloud 組合大PK

SpringCloud 有哪些主流組合呢?

  • SpringCloud-Alibaba
    • Sentinel:把流量作爲切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
    • Nacos:一個更易於構建雲原生應用的動態服務發現、配置管理和服務管理平臺。
    • RocketMQ:一款開源的分佈式消息系統,基於高可用分佈式集羣技術,提供低延時的、高可靠的消息發佈與訂閱服務。
    • Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。
    • Seata:阿里巴巴開源產品,一個易於使用的高性能微服務分佈式事務解決方案。
    • Alibaba Cloud ACM:一款在分佈式架構環境中對應用配置進行集中管理和推送的應用配置中心產品。
    • Alibaba Cloud OSS: 阿里雲對象存儲服務(Object Storage Service,簡稱 OSS),是阿里雲提供的海量、安全、低成本、高可靠的雲存儲服務。您可以在任何應用、任何時間、任何地點存儲和訪問任意類型的數據。
    • Alibaba Cloud SchedulerX: 阿里中間件團隊開發的一款分佈式任務調度產品,提供秒級、精準、高可靠、高可用的定時(基於 Cron 表達式)任務調度服務。
    • Alibaba Cloud SMS: 覆蓋全球的短信服務,友好、高效、智能的互聯化通訊能力,幫助企業迅速搭建客戶觸達通道。
  • SpringCloud-Netflix
    • Eureka :服務註冊和發現,它提供了一個服務註冊中心、服務發現的客戶端,還有一個方便的查看所有註冊的服務的界面。 所有的服務使用Eureka的服務發現客戶端來將自己註冊到Eureka的服務器上。
    • Zuul : 網關,所有的客戶端請求通過這個網關訪問後臺的服務。它可以使用一定的路由配置來判斷某一個URL由哪個服務來處理。並從Eureka獲取註冊的服務來轉發請求。
    • Ribbon :即負載均衡,Zuul網關將一個請求發送給某一個服務的應用的時候,如果一個服務啓動了多個實例,就會通過Ribbon來通過一定的負載均衡策略來發送給某一個服務實例。
    • Feign :服務客戶端,服務之間如果需要相互訪問,可以使用RestTemplate,也可以使用Feign客戶端訪問。它默認會使用Ribbon來實現負載均衡。
    • Hystrix : 監控和斷路器。我們只需要在服務接口上添加Hystrix標籤,就可以實現對這個接口的監控和斷路器功能。
    • Hystrix Dashboard : 監控面板,它提供了一個界面,可以監控各個服務上的服務調用所消耗的時間等。
    • Turbine : 監控聚合,使用Hystrix監控,我們需要打開每一個服務實例的監控信息來查看。而Turbine可以幫助我們把所有的服務實例的監控信息聚合到一個地方統一查看。

兩者的優缺點: SpringCloud 是一項標準而不是一門技術, 你可以在它們互相兼容的前提下同時使用兩大陣營的組件, 兩者最大的不同在於, Netflix 的服務通信基於 Feign 組件傾向於 HTTP RestFul, 而 Alibaba 的服務通信 基於 Dubbo 組件 的 RPC 調用, 從這裏不難看出來, 它們的基本盤分別是 SpringBoot 與 Dubbo, 如果你的項目基於 SpringBoot 就首選 Netflix , 如果你的項目基於 Dubbo 就首選 Alibaba 這樣對於重構系統來說會減少很多工作量, 從社區的角度看, 大家都知道 2018-12-12日,Netflix宣佈Spring Cloud Netflix 除了 Eureka 其它組件都進入維護狀態(不會推出新功能), 但不等於 Netflix 就毫無希望, 最近 Netflix 推出了 PRE 3.0 M1 對 Eureka 進行迭代, 而 Alibaba 這個後起之秀的 GitHub Fork 數爲 3.8 K 而 Netflix 爲 2K 近乎兩倍, 對擁有國內 70% 市場的 Netflix 來說 進入維護狀態的 組件可以, 用別的組件來替代就可以, 而如果使用了 Alibaba 就被被捆綁銷售了 一堆自己的技術以及阿里雲的東西 … 技術選型上面沒有銀彈, 選擇最適合項目的技術即可 !

img

SpringCloud-Alibaba 太香了 ,🥊 我選 SpringCloud-Netflix !

推薦 SpringCloud-Netflix組合

  1. 服務註冊與發現組件:Eureka,Zookeeper,Consul,Nacos等。Eureka基於REST風格的。

  2. 服務調用組件:Hystrix(熔斷降級,在出現依賴服務失效的情況下,通過隔離 系統依賴服務 的方式,防止服務級聯失敗,同時提供失敗回滾機制,使系統能夠更快地從異常中恢復),Ribbon(客戶端負載均衡,用於提供客戶端的軟件負載均衡算法,提供了一系列完善的配置項:連接超時、重試等),OpenFeign(優雅的封裝Ribbon,是一個聲明式RESTful網絡請求客戶端,它使編寫Web服務客戶端變得更加方便和快捷)。

  3. 網關:路由和過濾。Zuul,Gateway。

  4. 配置中心:提供了配置集中管理,動態刷新配置的功能;配置通過Git或者其它方式來存儲。

  5. 消息組件:Spring Cloud Stream(對分佈式消息進行抽象,包括髮布訂閱、分組消費等功能,實現了微服務之間的異步通信)和Spring Cloud Bus(主要提供服務間的事件通信,如刷新配置)

  6. 安全控制組件:Spring Cloud Security 基於OAuth2.0開放網絡的安全標準,提供了單點登錄、資源授權和令牌管理等功能。

  7. 鏈路追蹤組件:Spring Cloud Sleuth(收集調用鏈路上的數據),Zipkin(對Sleuth收集的信息,進行存儲,統計,展示)

SpringCloud-Netflix 中的微服務理念

微服務概念 技術棧 實現原理 類似方案
通過服務進行組件化: 組件是獨立可替換和可升級的軟件單元。 Eureka , OpenFeign 服務註冊與發現組件後使用服務調用組件,組件之間可以通過HTTP複用 Dubbo
分散治理: 集中治理的後果之一是傾向於在單一技術平臺上實現標準化。 Zuul,Eureka 通過網關路由給服務生產者, 網關,生產者可以擁有多實例 Nginx
分散數據管理: 那些具有相同屬性的屬性可能具有不同的語義,並且(更差的)公共屬性具有不同的語義。 SpringCloud Config ,Bus 提供了配置集中管理,動態刷新配置的功能;每個微服務都可以有自己的數據配置. yml
智能端點和啞管道: 一個很好的例子是企業服務總線(ESB),其中ESB產品通常包括用於消息路由,編排,轉換和應用業務規則的複雜工具。 SpringCloudStream 對分佈式消息進行抽象,包括髮布訂閱、分組消費等功能,實現了微服務之間的異步通信. ApplicationEvent
基礎設施自動化: 升級工作軟件的渠道意味着我們可以自動部署 到每個新環境。 Jenkins, Docker, ,SpringCloudSleuth, Zipkin 使用 Jenkins 進行 CI/CD到微服務到 Docker 後用 Sleuth,Zipkin 監控. elk
失敗設計: 使用服務作爲組件的結果是,需要對應用程序進行設計,以便它們可以容忍服務故障。 Hystrix Hystrix 熔斷降級,在出現依賴服務失效的情況下,通過隔離 系統依賴服務 的方式,防止服務級聯失敗,同時提供失敗回滾機制,使系統能夠更快地從異常中恢復. Nginx+Lua
進化設計: 組件的關鍵屬性是獨立替換和可升級性的概念[13] -這意味着我們尋找可以想象重寫組件而不影響其協作者的觀點。 k8s , Eureka, Ribbon,Sleuth,zipkin 我認爲這裏表述的是可維護性, 也就是說 運維能力, 使用Sleuth,zipkin 監控服務流量, 在熱點服務流量幾何形增長時用 k8s 進行擴容, 基於 Eureka 與 Ribbon 進行流量分發 OpenStack

SpringCloud-Netflix 組件源碼簡析

Eureka

註冊中心 (場景: 服務註冊與服務發現, 可以理解爲 IM服務器)

Eureka

  • Eureka Server: 同步複製 Eureka Client 元信息用於微服務註冊發現 , 支持多實例
  • Service Provider: 通過 Eureka Server 進行微服務註冊, 心跳續約, 下線通知, 支持多實例
  • Service Consumer: 通過 Eureka Server 發現微服務註冊信息, 支持多實例

從eureka-core 包中的 AbstractInstanceRegistry 類看起

* Handles all registry requests from eureka clients.
 *
 * 
 * Primary operations that are performed are the
 * Registers, Renewals, Cancels, Expirations, and Status Changes The
 * registry also stores only the delta operations
 * 
 * @author Karthik Ranganathan
 *
 */

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
            = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
  • 第一層的 Map , K 是應用名稱. V 是Map 多實例元信息
  • 第二層 Map 爲保持更多實例信息, K 爲 實例名稱, V 爲實例詳細信息(註冊信息,ip地址,實例id,端口, 狀態等)
  • Eureka 通過維護 這個 ConcurrentHashMap 實現服務註冊以及發現.

建議可以從 193行 public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) 微服務註冊方法看起


Zuul

API 網關 (場景: 動態路由, 監控, 可以理解爲 快遞攬件配送)

zuul

  • pre filters: 在調用 Origin Server 之前執行, 場景: 用於身份驗證. 記錄調試信息, 擇優選擇微服務等
  • routing filters: 調用 Origin Server 時執行, 場景: 用於請求微服務. 獲得響應等
  • post filters: 在調用 Origin Server 之後執行, 場景: 用於爲響應添加標準 HTTP 頭, 收集統計信息和指標, 將響應從微服務返回給客戶端等
  • error filters: 在調用 Origin Server 過程中, 場景: 獲取錯誤信息跳轉 post filters.
  • custom filters: 在調用 Origin Server 任意場景執行, 場景: 自定義過濾需求

從 org.springframework.cloud.netflix.zuul.filters.support FilterConstants 類看起

這個類中是有和 上面的Filters 相關的常量

// Zuul Filter TYPE constants -----------------------------------

    /**
     * {@link ZuulFilter#filterType()} error type.
     */
    public static final String ERROR_TYPE = "error";

    /**
     * {@link ZuulFilter#filterType()} post type.
     */
    public static final String POST_TYPE = "post";

    /**
     * {@link ZuulFilter#filterType()} pre type.
     */
    public static final String PRE_TYPE = "pre";

    /**
     * {@link ZuulFilter#filterType()} route type.
     */
    public static final String ROUTE_TYPE = "route";

    // OTHER constants -----------------------------------

對這幾個 常量全局搜索 會找到與上述功能相同的過濾器類
PreDecorationFilter
SendForwardFilter
SendResponseFilter
SendErrorFilter

PreDecorationFilter 128行 初始化請求參數映射

        RequestContext ctx = RequestContext.getCurrentContext(); ...

之後 通過轉發過濾實現上述功能 !

RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);

RestTemplate

RestTemplate (場景: 簡化 HTTP 通信方式,統一了RESTful的標準併爲 執行復雜任務提供了一種具有默認行爲的簡化方法, 可以理解爲 GOF中的 模板模式)

RestTemplate

  • HttpMessageConverter: 對象轉換器
  • ClientHttpRequestFactory: 提供了多種便捷訪問遠程Http服務的方法,能夠大大提高客戶端的編寫效率。
  • ResponseErrorHandler: 異常處理
  • ClientHttpRequestInterceptor: 請求攔截器

Spring 核心 HTTP 消息轉換器 HttpMessageConverter

Rest自描述信息: 媒體類型 (MediaType) : text/html; text/xml; application/json

HTTP 協議特點: 純文本協議 ,需要自我描述

  • REST 客戶端
  • REST 服務端

反序列化 : 文本(通信) —> 對象(程序使用)

序列化: 對象(程序) ----> 文本(通信)

分析 HttpMessageConverter

// 策略接口,它指定了一個轉換器,可以將請求和響應轉換爲HTTP請求和響應。 
public interface HttpMessageConverter<T> {
	// 判斷當前<T> 泛型是否可以反序列化
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
    // 判斷當前<T> 泛型是否可以序列化
	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    // 當前支持的媒體類型
   	List<MediaType> getSupportedMediaTypes();
    // 反序列化對象
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)	throws IOException, HttpMessageNotReadableException;
}

特別提醒: SpringWebMVC 依賴於 Servlet, Spring 在設計早期時, 它就考慮到了去 Servlet 化.

HttpInputMessage 類似於 HttpServletRequest

HttpMessageConverter

RestTemplate 利用 HttpMessageConverter 對一些媒體類型進行通用的序列化和反序列化

  • JSON
  • XML
  • TEXT
  • ByteArrays

它不依賴於 Servlet 它自定義實現, 對於 服務端而言. 將 ServletAPI 適配或 HttpInputMessage 以及 HttpOutputMessage .


RestTemplate 對應多個 HttpMessageConverter 那麼如何決策正確的媒體類型

將各種常用 HttpMessageConverter 支持的MediaType 和 JavaType 以及對應關係總結在此處:

類名 支持的JavaType 支持的MediaType
ByteArrayHttpMessageConverter byte[] application/octet-stream, /
StringHttpMessageConverter String text/plain, /
MappingJackson2HttpMessageConverter Object application/json, application/*+json
AllEncompassingFormHttpMessageConverter Map<K, List<?>> application/x-www-form-urlencoded, multipart/form-data
SourceHttpMessageConverter Source application/xml, text/xml, application/*+xml

RestTemplate 對應多個 HttpMessageConverter , 那麼如何決策正確媒體類型 ?

   // 同志們 順着方法調用棧追蹤源碼. 別怕, 前方安全, 有註釋 ! 
   // 建議 Debug 行點. 
   // RestTemplate: 190,419,673,769,850  
   // HttpMessageConverterExtractor: 89     
  @Test 
   public void getForObject() throws Exception {
      RestTemplate restTemplate = new RestTemplate(
       new HttpComponentsClientHttpRequestFactory());
       // restTemplate = new RestTemplate();
       // restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
       // 設置攔截器記錄HTTP請求到響應時間
       restTemplate.setInterceptors(Arrays.asList(new TimeInterceptor()));
       String result = restTemplate.getForObject("https://example.com",String.class);
		
    }

// 記錄響應時間攔截器 
 class TimeInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
  // 舉一反三: 也可以在請求前進行負載均衡到具體 IP
  long frontNow = System.currentTimeMillis();
  ClientHttpResponse response = execution.execute(request, body);
  // 獲取請求消耗時間
 System.out.println("消耗時間"+ (System.currentTimeMillis() - frontNow) / 1000 + "秒");

     return response;
    }
}

從 SpringWeb包 org.springframework.web.client.RestTemplate 類看起

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations{
   private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();

  static {	
    // 初始化時 判斷 第三方 HttpMessageConverter 實現是否存在   
    ClassLoader classLoader = RestTemplate.class.getClassLoader(); 
    jackson2XmlPresent =                ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);     gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
           ...
   }
                                                                                       public RestTemplate() {
       
      // 存在的默認內置 HttpMessageConverter以及第三方實現 按順序裝入 messageConverters 
                                                                                           this.messageConverters.add(new ByteArrayHttpMessageConverter());

  if (jackson2XmlPresent) {
		this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
  }
                                                                                       if(..){...}
                                                                                        ....
                                                                                       }  
    
public RestTemplate(ClientHttpRequestFactory requestFactory) {
        this();
        // 設置 requestFactory 適配器進行 http 請求
        this.setRequestFactory(requestFactory);
  }

 @Override
 @Nullable
 public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables)  throws RestClientException {
    // 用期望 返回對象.class 初始 RequestCallback 對象, 響應後的用於反序列化
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
 	// 根據 HttpMessageConverter 初始化 HttpMessageConverterExtractor 用來處理拿到響應後的反序列化策略
     HttpMessageConverterExtractor<T> responseExtractor = 
    new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
     // 調用抽象執行層
return execute(url, HttpMethod.GET, requestCallback, responseExtractor,uriVariables);
 }

@Override
@Nullable
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,	@Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {
    // 將 URL 與 動態參數 拼裝爲 真實URL
	URI expanded = getUriTemplateHandler().expand(url, uriVariables);
	return doExecute(expanded, method, requestCallback, responseExtractor);  
}
    
@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
		@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
	// URL 與 method 不可爲空
	Assert.notNull(url, "URI is required");
	Assert.notNull(method, "HttpMethod is required");
	ClientHttpResponse response = null;
	try {
        // 創建通用 ClientHttpRequestFactory 請求對象
		ClientHttpRequest request = createRequest(url, method);
		if (requestCallback != null) {
            //  給請求頭 Accept 設置 可序列化的 HttpMessageConverter.MediaType 策略  
            requestCallback.doWithRequest(request);
		}
       // 執行請求攔截器鏈並使用 ClientHttpRequestFactory 適配實現類發送請求, 獲取 響應文本報文
        response = request.execute();
        // 處理給定的響應,執行適當的日誌記錄並調用 ResponseErrorHandler 處理異常
		handleResponse(url, method, response);
        // 使用 extractData() 將文本數據按 messageConverters匹配順序反序列化爲 期望返回對象
		return (responseExtractor != null ? responseExtractor.extractData(response) : null);
	}
	catch (IOException ex) {
		String resource = url.toString();
		String query = url.getRawQuery();
		resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
		throw new ResourceAccessException("I/O error on " + method.name() +
				" request for \"" + resource + "\": " + ex.getMessage(), ex);
	}
	finally {
		if (response != null) {
            // 因爲當前 response 對象是 接口所以無法使用 jdk7自動關閉流, 需手動關閉
			response.close();
		}
	}
}  
   public void doWithRequest(ClientHttpRequest request) throws IOException {
        if (this.responseType != null) {
        List<MediaType> allSupportedMediaTypes = (List)RestTemplate.this.getMessageConverters().stream().filter((converter) -> {
       return this.canReadResponse(this.responseType, converter);
}).flatMap(this::getSupportedMediaTypes).distinct().sorted(MediaType.SPECIFICITY_COMPARATOR).collect(Collectors.toList());
            // debug 模式 打印日誌
             if (RestTemplate.this.logger.isDebugEnabled()) {
                RestTemplate.this.logger.debug("Accept=" + allSupportedMediaTypes);
             }
            
              //  給請求頭 Accept 設置 可序列化的 HttpMessageConverter.MediaType 策略  
              request.getHeaders().setAccept(allSupportedMediaTypes);
            }
        }
}
  // 其實可以看出來 源碼閱讀不是很難, 讀者朋友們以後可以和面試官吹你看過 spring http 的核心源碼了 

OpenFeign

TODO 發表後一週內更新

下期預告-(二) JHipster 讓 SpringCloud架構變得簡單

掃碼 20K+ 回覆 "JHipsterMicroservice" 獲取SpringCloud思維雙導圖

掃碼回覆 “加羣” 和我一起月入 20K+

深入淺出分享 Java 乾貨 , 找回對代碼的 Passion , 助力月入 20K+

祝全世界勞動者們,五一快樂!

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