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

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

答案: 微服務架構是單機架構的將來, 但不是銀彈多用於增加型業務!
若是你是一位軟件行業從業者,尤爲是從事服務器端或者後臺系統軟件開發,相信近年來必定被層出不窮的商業名詞所包圍:NoSQL、Big Data、Web-scale、Sharding、Eventual consistency、ACID、CAP理論、雲服務、MapReduce和Real-time等, 全部這些其實都圍繞着如何構建高效存儲與數據處理這一核心主題。

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

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

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

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

良心推薦人手一本 -&gt; Designing Data-Intensive Applications

先有SpringCloud 仍是先有 微服務 ?

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

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

早期 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 的熱情一模一樣, 可是那時候 微服務這個概念並非很火熱 !spring

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 有那麼多組合咱們選哪一個好呢? apache

### 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+
相關文章
相關標籤/搜索