HttpClient、RestTemplate和Feign相關知識


#先了解一下HTTP 協議

史前時期

   HTTP 協議在我們的生活中隨處可見,打開手機或者電腦,只要你上網,不論是用 iPhone、Android、Windows 還是 Mac,不論是用瀏覽器還是 App,不論是看新聞、短視頻還是聽音樂、玩游戲,后面總會有 HTTP 在默默為你服務。

  據 NetCraft 公司統計,目前全球至少有 16 億個網站、2 億多個獨立域名,而這個龐大網絡世界的底層運轉機制就是 HTTP。

  那么,在享受如此便捷舒適的網絡生活時,你有沒有想過,HTTP 協議是怎么來的?它最開始是什么樣子的?又是如何一步一步發展到今天,幾乎“統治”了整個互聯網世界的呢?

  20 世紀 60 年代,美國國防部高等研究計划署(ARPA)建立了 ARPA 網,它有四個分布在各地的節點,被認為是如今互聯網的“始祖”。

  然后在 70 年代,基於對 ARPA 網的實踐和思考,研究人員發明出了著名的 TCP/IP 協議。由於具有良好的分層結構和穩定的性能,TCP/IP 協議迅速戰勝其他競爭對手流行起來,並在 80 年代中期進入了 UNIX 系統內核,促使更多的計算機接入了互聯網。

創世紀

  1989 年,任職於歐洲核子研究中心(CERN)的蒂姆·伯納斯 - 李(Tim Berners-Lee)發表了一篇論文,提出了在互聯網上構建超鏈接文檔系統的構想。這篇論文中他確立了三項關鍵技術。

  • URI:即統一資源標識符,作為互聯網上資源的唯一身份;
  • HTML:即超文本標記語言,描述超文本文檔;
  • HTTP:即超文本傳輸協議,用來傳輸超文本。

  所以在這一年,我們的HTTP誕生了。

HTTP/0.9

  20 世紀 90 年代初期的互聯網世界非常簡陋,計算機處理能力低,存儲容量小,網速很慢,還是一片“信息荒漠”。網絡上絕大多數的資源都是純文本,很多通信協議也都使用純文本,所以 HTTP 的設計也不可避免地受到了時代的限制。

  這一時期的 HTTP 被定義為 0.9 版,結構比較簡單,為了便於服務器和客戶端處理,它也采用了純文本格式。蒂姆·伯納斯 - 李最初設想的系統里的文檔都是只讀的,所以只允許用“GET”動作從服務器上獲取 HTML 文檔,並且在響應請求之后立即關閉連接,功能非常有限。

  HTTP/0.9 雖然很簡單,但它作為一個“原型”,充分驗證了 Web 服務的可行性,而“簡單”也正是它的優點,蘊含了進化和擴展的可能性,因為:

  “把簡單的系統變復雜”,要比“把復雜的系統變簡單”容易得多。

HTTP/1.0

  1993 年,NCSA(美國國家超級計算應用中心)開發出了 Mosaic,是第一個可以圖文混排的瀏覽器,隨后又在 1995 年開發出了服務器軟件 Apache,簡化了 HTTP 服務器的搭建工作。

  同一時期,計算機多媒體技術也有了新的發展:1992 年發明了 JPEG 圖像格式,1995 年發明了 MP3 音樂格式。

  這些新軟件新技術一經推出立刻就吸引了廣大網民的熱情,更的多的人開始使用互聯網,研究 HTTP 並提出改進意見,甚至實驗性地往協議里添加各種特性,從用戶需求的角度促進了 HTTP 的發展。

  於是在這些已有實踐的基礎上,經過一系列的草案,HTTP/1.0 版本在 1996 年正式發布。它在多方面增強了 0.9 版,形式上已經和我們現在的 HTTP 差別不大了,例如:

  • 增加了 HEAD、POST 等新方法
  • 增加了響應狀態碼,標記可能的錯誤原因
  • 引入了協議版本號概念
  • 引入了 HTTP Header(頭部)的概念,讓 HTTP 處理請求和響應更加靈活
  • 傳輸的數據不再僅限於文本

HTTP/1.1

  1995 年,網景的 Netscape Navigator 和微軟的 Internet Explorer 開始了著名的“瀏覽器大戰”,都希望在互聯網上占據主導地位。於是在“瀏覽器大戰”結束之后的 1999 年,HTTP/1.1 發布了 RFC 文檔,編號為 2616,正式確立了延續十余年的傳奇。

  HTTP/1.1 主要的變更點有:

  • 增加了 PUT、DELETE 等新的方法;
  • 增加了緩存管理和控制;
  • 明確了連接管理,允許持久連接;
  • 允許響應數據分塊(chunked),利於傳輸大文件;
  • 強制要求 Host 頭,讓互聯網主機托管成為可能。

HTTP/2

  HTTP/1.1 發布之后,整個互聯網世界呈現出了爆發式的增長,度過了十多年的“快樂時光”,更涌現出了 Facebook、Twitter、淘寶、京東等互聯網新貴。

  這期間也出現了一些對 HTTP 不滿的意見,主要就是連接慢,無法跟上迅猛發展的互聯網,但 HTTP/1.1 標准一直“巋然不動”,無奈之下人們只好發明各式各樣的“小花招”來緩解這些問題,比如以前常見的切圖、JS 合並等網頁優化手段。

  終於有一天,搜索巨頭 Google 忍不住了,首先開發了自己的瀏覽器 Chrome,然后推出了新的 SPDY 協議,並在 Chrome 里應用於自家的服務器,如同十多年前的網景與微軟一樣,從實際的用戶方來“倒逼”HTTP 協議的變革,這也開啟了第二次的“瀏覽器大戰”。

  歷史再次重演,不過這次的勝利者是 Google,Chrome 目前的全球的占有率超過了 60%。Google 借此順勢把 SPDY 推上了標准的寶座,互聯網標准化組織以 SPDY 為基礎開始制定新版本的 HTTP 協議,最終在 2015 年發布了 HTTP/2,RFC 編號 7540。

SPDY(讀作“SPeeDY”)是Google開發的基於TCP的會話層協議,用以最小化網絡延遲,提升網絡速度,優化用戶的網絡使用體驗。
SPDY並不是一種用於替代HTTP的協議,而是對HTTP協議的增強。新協議的功能包括數據流的多路復用、請求優先級以及HTTP報頭壓縮。
谷歌表示,引入SPDY協議后,在實驗室測試中頁面加載速度比原先快64%。

  HTTP/2 的制定充分考慮了現今互聯網的現狀:寬帶、移動、不安全,在高度兼容 HTTP/1.1 的同時在性能改善方面做了很大努力,主要的特點有:

  • 二進制協議,不再是純文本
  • 可發起多個請求,廢棄了 1.1 里的管道
  • 使用專用算法壓縮頭部,減少數據傳輸量
  • 允許服務器主動向客戶端推送數據
  • 增強了安全性,“事實上”要求加密通信

  雖然 HTTP/2 到今天已經五歲,也衍生出了 gRPC 等新協議,但由於 HTTP/1.1 實在是太過經典和強勢,目前它的普及率還比較低,大多數網站使用的仍然還是 20 年前的 HTTP/1.1。

HTTP/3

  在 HTTP/2 還處於草案之時,Google 又發明了一個新的協議,叫做 QUIC,而且還是相同的“套路”,繼續在 Chrome 和自家服務器里試驗着“玩”,依托它的龐大用戶量和數據量,持續地推動 QUIC 協議成為互聯網上的“既成事實”。

  2018 年,互聯網標准化組織 IETF 提議將“HTTP over QUIC”更名為“HTTP/3”並獲得批准,HTTP/3 正式進入了標准化制訂階段,也許兩三年后就會正式發布,到時候我們很可能會跳過 HTTP/2 直接進入 HTTP/3。

QUIC(Quick UDP Internet Connection)是谷歌制定的一種基於UDP的低時延的互聯網傳輸層協議。
在2016年11月國際互聯網工程任務組(IETF)召開了第一次QUIC工作組會議,受到了業界的廣泛關注。
這也意味着QUIC開始了它的標准化過程,成為新一代傳輸層協議

了解這么多,那到底HTTP是什么呢?

  你可能會不假思索、脫口而出:“HTTP 就是超文本傳輸協議,也就是 HyperText Transfer Protocol。”

回答的也沒錯,但是太過簡單。更准確的回答應該是“HTTP 是一個在計算機世界里專門在兩點之間傳輸文字、圖片、音頻、視頻等超文本數據的約定和規范”

 

#關於HttpClient

簡介  

  官網這樣說

超文本傳輸​​協議(HTTP)可能是當今Internet上使用的最重要的協議。
Web服務,支持網絡的設備和網絡計算的增長繼續將HTTP協議的作用擴展到用戶驅動的Web瀏覽器之外,同時增加了需要HTTP支持的應用程序的數量。 盡管java.net軟件包提供了用於通過HTTP訪問資源的基本功能,但它並未提供許多應用程序所需的全部靈活性或功能。
HttpClient試圖通過提供高效,最新且功能豐富的程序包來實現此空白,以實現最新HTTP標准和建議的客戶端。 HttpClient是為擴展而設計的,同時提供了對基本HTTP協議的強大支持,
對於構建HTTP感知的客戶端應用程序(例如Web瀏覽器,Web服務客戶端或利用或擴展HTTP協議進行分布式通信的系統)的任何人來說,HttpClient都可能會感興趣。
  HttpClient 是 Apache Jakarta Common 下的子項目,用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,並且它支持 HTTP 協議最新的版本和建議。HttpClient 已經應用在很多的項目中,比如 Apache Jakarta 上很著名的另外兩個開源項目 Cactus 和 HTMLUnit 都使用了 HttpClient。
  
  HttpClient 相比傳統 JDK 自帶的 URLConnection,增加了易用性和靈活性,它不僅是客戶端發送 HTTP 請求變得容易,而且也方便了開發人員測試接口(基於 HTTP 協議的),即提高了開發的效率,也方便提高代碼的健壯性。因此熟練掌握 HttpClient 是很重要的必修內容,掌握 HttpClient 后,相信對於 HTTP 協議的了解會更加深入。
  
  了解更新詳情,參考官網 http://hc.apache.org/index.html
 
特性
  • 基於標准、純凈的 Java 語言。實現了 HTTP 1.0 和 HTTP 1.1
  • 以可擴展的面向對象的結構實現了 HTTP 全部的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, and TRACE)。
  • 支持 HTTPS 協議。
  • 通過 HTTP 代理建立透明的連接。
  • 利用 CONNECT 方法通過 HTTP 代理建立隧道的 HTTPS 連接。
  • Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO/Kerberos 認證方案。
  • 插件式的自定義認證方案。
  • 便攜可靠的套接字工廠使它更容易的使用第三方解決方案。
  • 連接管理器支持多線程應用。支持設置最大連接數,同時支持設置每個主機的最大連接數,發現並關閉過期的連接。
  • 自動處理 Set-Cookie 中的 Cookie。
  • 插件式的自定義 Cookie 策略。
  • Request 的輸出流可以避免流中內容直接緩沖到 Socket 服務器。
  • Response 的輸入流可以有效的從 Socket 服務器直接讀取相應內容。
  • 在 HTTP 1.0 和 HTTP 1.1 中利用 KeepAlive 保持持久連接。
  • 直接獲取服務器發送的 response code 和 headers。
  • 設置連接超時的能力。
  • 實驗性的支持 HTTP 1.1 response caching。
  • 源代碼基於 Apache License 可免費獲取。
使用流程
  1. 創建 HttpClient 對象
  2. 創建請求方法的實例,並指定請求 URL。如果需要發送 GET 請求,創建 HttpGet 對象;如果需要發送 POST 請求,創建 HttpPost 對象
  3. 如果需要發送請求參數,可調用 HttpGetHttpPost 共同的 setParams(HttpParams params) 方法來添加請求參數;對於 HttpPost 對象而言,也可調用 setEntity(HttpEntity entity) 方法來設置請求參數
  4. 調用 HttpClient 對象的 execute(HttpUriRequest request) 發送請求,該方法返回一個 HttpResponse
  5. 調用 HttpResponse 的 getAllHeaders()getHeaders(String name) 等方法可獲取服務器的響應頭
  6. 調用 HttpResponse 的 getEntity() 方法可獲取 HttpEntity 對象,該對象包裝了服務器的響應內容。程序可通過該對象獲取服務器的響應內容
  7. 釋放連接。無論執行方法是否成功,都必須釋放連接

使用用例

  pom配置

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.12</version>
        </dependency>

  創建Get請求

  
@Test
void testGet() {
// 創建 HttpClient 客戶端
CloseableHttpClient httpClient = HttpClients.createDefault();
// 創建 HttpGet 請求
HttpGet httpGet = new HttpGet("http://192.168.1.250:15005/dsm-ubm/base-role-info/list");
// 設置長連接
httpGet.setHeader("Connection", "keep-alive");
// 設置認證信息
httpGet.setHeader("Authorization", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiY3JlYXRlZCI6MTU4ODA2NjM1NTMxNiwiZXhwIjoxNTg4NjcxMTU1fQ.mfroxGQMf_QbHGViEBhQ0hzHoxdNM0TwpGWT64t3LPUl8Sn_ZSBFFKUAt0aKkywM3Lq8245LSXu6BYOptVwYZg");
// 設置代理(模擬瀏覽器版本)
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");

CloseableHttpResponse httpResponse = null;
try {
// 請求並獲得響應結果
httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
//打印結果
System.out.println(EntityUtils.toString(httpEntity));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (httpResponse != null) {
try {
httpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

   keep-alive說明

keep-alive:從HTTP/1.1起,瀏覽器默認都開啟了Keep-Alive,保持連接特性,客戶端和服務器都能選擇隨時關閉連接,則請求頭中為connection:close。
簡單地說,當一個網頁打開完成后,客戶端和服務器之間用於傳輸HTTP數據的TCP連接不會關閉,如果客戶端再次訪問這個服務器上的網頁,會繼續使用這一條已經建立的TCP連接。
但是Keep-Alive不會永久保持連接,它有一個保持時間,可以在不同的服務器軟件(如Apache)中設定這個時間。

  創建Post請求,content-type=application/json

@Test
    void testPost() {
        // 創建 HttpClient 客戶端
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 創建 HttpPost 請求
        HttpPost httpPost = new HttpPost("http://192.168.1.250:15005/dsm-ubm/api/getInAreaPatientList");
        // 設置長連接
        httpPost.setHeader("Connection", "keep-alive");
        // 設置認證信息
        httpPost.setHeader("Authorization", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiY3JlYXRlZCI6MTU4ODA2NjM1NTMxNiwiZXhwIjoxNTg4NjcxMTU1fQ.mfroxGQMf_QbHGViEBhQ0hzHoxdNM0TwpGWT64t3LPUl8Sn_ZSBFFKUAt0aKkywM3Lq8245LSXu6BYOptVwYZg");
        // 設置代理(模擬瀏覽器版本)
        httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");

        // 創建 HttpPost 參數
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("FromTime", "");
        jsonObject.put("Ward", "4008");

        CloseableHttpResponse httpResponse = null;
        try {
            StringEntity entity = new StringEntity(jsonObject.toJSONString(), "utf-8");
            entity.setContentType("application/json");
            httpPost.setEntity(entity);
            httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            //打印結果
            System.out.println(EntityUtils.toString(httpEntity));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (httpResponse != null) {
                    httpResponse.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

   創建Post請求,content-type=application/x-www-form-urlencoded

@Test
    void testPost() {
        // 創建 HttpClient 客戶端
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 創建 HttpPost 請求
        HttpPost httpPost = new HttpPost("http://localhost:8080/hello");
        // 設置長連接
        httpPost.setHeader("Connection", "keep-alive");
        // 設置認證信息
        httpPost.setHeader("Authorization", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiY3JlYXRlZCI6MTU4ODA2NjM1NTMxNiwiZXhwIjoxNTg4NjcxMTU1fQ.mfroxGQMf_QbHGViEBhQ0hzHoxdNM0TwpGWT64t3LPUl8Sn_ZSBFFKUAt0aKkywM3Lq8245LSXu6BYOptVwYZg");
        // 設置代理(模擬瀏覽器版本)
        httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");

        // 創建 HttpPost 參數
        List<BasicNameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("message", "鷓鴣哨"));

        CloseableHttpResponse httpResponse = null;
        try {
            httpPost.setEntity(new UrlEncodedFormEntity(params, "utf-8"));
            httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            //打印結果
            System.out.println(EntityUtils.toString(httpEntity));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (httpResponse != null) {
                    httpResponse.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

 

application/json與application/x-www-form-urlencoded區別

  application/json和application/x-www-form-urlencoded都是表單數據發送時的編碼類型。默認地,表單數據會編碼為application/x-www-form-urlencoded。就是說,在發送到服務器之前,所有字符都會進行編碼。

  application/json,隨着json規范的越來越流行,並且瀏覽器支持程度原來越好,許多開發人員將application/json作為請求content-type,告訴服務器請求的主體內容是json格式的字符串,服務器端會對json字符串進行解析,這種方式的好處就是前端人員不需要關心數據結構的復雜度,只要是標准的json格式就能提交成功,需要封裝成對象的話,可以加上@RequestBody注解

  application/x-www-form-urlencoded是Jquery的Ajax請求默認方式,這種方式的好處就是瀏覽器都支持,在請求發送過程中會對數據進行序列化處理,以鍵值對形式,數據拼接方式為key=value的方式,后台如果使用對象接收的話,可以自動封裝成對象

 
@RequestBody和@RequestParam區別

@RequestParam

  1. 用來處理Content-Type: 為 application/x-www-form-urlencoded編碼的內容。(Http協議中,如果不指定Content-Type,則默認傳遞的參數就是application/x-www-form-urlencoded類型)。
  2. 在Content-Type: application/x-www-form-urlencoded的請求中, get 方式中queryString的值,和post方式中 body data的值都會被Servlet接受到並轉化到Request.getParameter()參數集中,所以@RequestParam可以獲取的到。

@RequestBody

  1. 處理HttpEntity傳遞過來的數據,一般用來處理非Content-Type: application/x-www-form-urlencoded編碼格式的數據。
  2. GET請求中,因為沒有HttpEntity,所以@RequestBody並不適用。
  3. POST請求中,通過HttpEntity傳遞的參數,必須要在請求頭中聲明數據的類型Content-Type,SpringMVC通過使用HandlerAdapter 配置的HttpMessageConverters來解析HttpEntity中的數據,然后綁定到相應的bean上。

 

@ResponseBody 和 @RequestBody 區別

  @ResponseBody是作用在方法上的,@ResponseBody 表示該方法的返回結果直接寫入 HTTP response body 中,一般在異步獲取數據時使用【也就是AJAX】,在使用 @RequestMapping后,返回值通常解析為跳轉路徑,但是加上 @ResponseBody 后返回結果不會被解析為跳轉路徑,而是直接寫入 HTTP response body 中。 比如異步獲取 json 數據,加上 @ResponseBody 后,會直接返回 json 數據。

  @RequestBody 用於讀取Request請求的body部分數據,使系統默認的HttpMessageConverter進行解析,然后把相應的數據綁定要返回的對象上,再把HttpMessageConverter返回的對象數據綁定到Controller方法的參數上。

 

#關於RestTemplate

介紹

  RestTemplate 是從 Spring3.0 開始支持的一個 HTTP 請求工具,它提供了常見的REST請求方案的模版,例如 GET 請求、POST 請求、PUT 請求、DELETE 請求以及一些通用的請求執行方法 exchange 以及 execute。RestTemplate 繼承自 InterceptingHttpAccessor 並且實現了 RestOperations 接口,其中 RestOperations 接口定義了基本的 RESTful 操作,這些操作在 RestTemplate 中都得到了實現。是比httpClient更優雅的Restful URL訪問。

  • RestTemplate是Spring提供的用於訪問Rest服務的客戶端,
  • RestTemplate提供了多種便捷訪問遠程Http服務的方法,能夠大大提高客戶端的編寫效率。
  • 調用RestTemplate的默認構造函數,RestTemplate對象在底層通過使用java.net包下的實現創建HTTP 請求,
  • 可以通過使用ClientHttpRequestFactory指定不同的HTTP請求方式。
  • ClientHttpRequestFactory接口主要提供了三種實現方式
  • 1、SimpleClientHttpRequestFactory方式,此處生成SimpleBufferingClientHttpRequest,使用HttpURLConnection創建底層的Http請求連接
  • 2、HttpComponentsClientHttpRequestFactory方式,此處生成HttpComponentsClientHttpRequest,使用http client來實現網絡請求
  • 3、OkHttp3ClientHttpRequestFactory方式,此處生成OkHttp3ClientHttpRequest,使用okhttp來實現網絡請求

優點

  • 並沒有重寫底層的HTTP請求技術,而是提供配置,可選用OkHttp/HttpClient等
  • 在OkHttp/HttpClient之上,封裝了請求操作,可以定義Convertor來實現對象到請求body的轉換方法,以及返回body到對象的轉換方法。

配置

@Configuration
public class RestTemplateConfig {

@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}

@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
//單位為ms
factory.setReadTimeout(5000);
//單位為ms
factory.setConnectTimeout(5000);
return factory;
}

@Primary
@Bean
public ClientHttpRequestFactory httpComponentsClientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
//單位為ms
factory.setReadTimeout(6000);
//單位為ms
factory.setConnectTimeout(6000);
return factory;
}

/*@Bean
public ClientHttpRequestFactory okHttp3ClientHttpRequestFactory() {
OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory();
//單位為ms
factory.setReadTimeout(7000);
//單位為ms
factory.setConnectTimeout(7000);
return factory;
}*/

}

使用

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestGet {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testGet() {
        String url = "http://localhost:8080/welcome?message={1}";
        //可以使用map來封裝請求參數
        Map<String, String> map = new HashMap<>();
        map.put("1", "world");
        String jsonResult = restTemplate.getForObject(url, String.class, map);
        System.out.println("result:" + jsonResult);
    }

}
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestPost {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testPost() {
        RequestObj requestObj = RequestObj.builder().id(1)
                .age(20)
                .name("鷓鴣哨").build();
        String url = "http://localhost:8080/test";
        //發起請求
        String jsonResult = restTemplate.postForObject(url, requestObj, String.class);
        System.out.println("result:" + jsonResult);
    }
}

 

#關於Feign

介紹

  Feign 的英文表意為“假裝,偽裝,變形”, 是一個http請求調用的輕量級框架,可以以Java接口注解的方式調用Http請求,而不用像Java中通過封裝HTTP請求報文的方式直接調用。Feign通過處理注解,將請求模板化,當實際調用的時候,傳入參數,根據參數再應用到請求上,進而轉化成真正的請求,這種請求相對而言比較直觀。
  Feign被廣泛應用在Spring Cloud 的解決方案中,是學習基於Spring Cloud 微服務架構不可或缺的重要組件。
 
 具體詳情參考 https://github.com/OpenFeign/feign
 
pom配置
<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    <!-- feign底層采用的http請求方式 不加則默認使用JDK的HttpURLConnection -->
    <dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    </dependency>
</dependencies>

 

Feign配置

@Configuration
public class FeignConfig {
 
    @Bean
    Logger.Level feignLoggerLevel() {
        //記錄請求和響應的標頭,正文和元數據
        return Logger.Level.FULL;
    }
 
    /**
     * 如果遠程接口由於各種問題沒有在響應中設置content-type,
     * 導致FeignClient接收的時候contentType為null,HttpMessageConverterExtractor將其設置為MediaType.APPLICATION_OCTET_STREAM
     * 此時MessageConverter需要增加MediaType.APPLICATION_OCTET_STREAM支持
     */
    @Bean
    public Decoder feignDecoder() {
        MappingJackson2HttpMessageConverter hmc = new MappingJackson2HttpMessageConverter(customObjectMapper());
        List<MediaType> unModifiedMediaTypeList = hmc.getSupportedMediaTypes();
        List<MediaType> mediaTypeList = new ArrayList<>(unModifiedMediaTypeList.size() + 1);
        mediaTypeList.addAll(unModifiedMediaTypeList);
        mediaTypeList.add(MediaType.APPLICATION_OCTET_STREAM);
        hmc.setSupportedMediaTypes(mediaTypeList);
        ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(hmc);
        return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
    }
@Bean
public ObjectMapper customObjectMapper() {

     //解決LocalDate、LocalDateTime反序列化問題

      ObjectMapper objectMapper = new ObjectMapper();

      objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

       objectMapper.registerModule(new JavaTimeModule());

return new ObjectMapper();
    }
}

 

啟用feign客戶端

@SpringBootApplication
@EnableFeignClients
public class DemoHttpApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(DemoHttpApplication.class, args);
    }
 
}

 

定義feign客戶端

@FeignClient(name = "test-service", path = "/", url = "http://localhost:8080")
public interface TestClient {

    @GetMapping("/welcome")
    String welcome(@RequestParam String message);

    @PostMapping("/test")
    String test(@RequestBody RequestObj param);

}

 

測試調用

@RunWith(SpringRunner.class)
@SpringBootTest
public class FeignTest {

    @Autowired
    private TestClient testClient;

    @Test
    public void testGet() {
        System.out.println("result:" + testClient.welcome("world"));
    }

    @Test
    public void testPost() {
        RequestObj requestObj = RequestObj.builder().id(1)
                .age(20)
                .name("鷓鴣哨").build();
        System.out.println("result:" + testClient.test(requestObj));
    }

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM