Forest輕量級HTTP客戶端API框架,該丟棄HttpClient了
一、前言
最近在碼云上扒了一波,發現了一個非常優秀的開源的輕量級HTTP客戶端API框架Forest,這款API框架讓Java發送HTTP/HTTPS請求不再難,他比原先了OkHttp和HttpClient更高層,以前在調用一個第三方外部API接口時,你可能需要使用HTTPClient或者OkHttp工具來實現,封裝一個HTTPClientUtil工具類,工具類中封裝一些Post/Get請求,那么現在你完全不需要這么做了,使用Forest框架只需要在你的接口上面加一個注解即可實現第三方API接口的調用。
二、Forest簡介
1.簡介
輕量級HTTP客戶端API框架,讓Java發送HTTP/HTTPS請求不再難。它比OkHttp和HttpClient更高層,是封裝調用第三方restful api client接口的好幫手,是retrofit和feign之外另一個選擇。
- 項目主頁: http://forest.dtflyx.com/
- 中文文檔: http://forest.dtflyx.com/docs/
2.什么是 Forest?
orest 是一個開源的 Java HTTP 客戶端框架,它能夠將 HTTP 的所有請求信息(包括 URL、Header 以及 Body 等信息)綁定到您自定義的 Interface 方法上,能夠通過調用本地接口方法的方式發送 HTTP 請求。
3. 為什么使用 Forest?
使用 Forest 就像使用類似 Dubbo 那樣的 RPC 框架一樣,只需要定義接口,調用接口即可,不必關心具體發送 HTTP 請求的細節。同時將 HTTP 請求信息與業務代碼解耦,方便您統一管理大量 HTTP 的 URL、Header 等信息。而請求的調用方完全不必在意 HTTP 的具體內容,即使該 HTTP 請求信息發生變更,大多數情況也不需要修改調用發送請求的代碼。
4.Forest 的工作原理
Forest 會將您定義好的接口通過動態代理的方式生成一個具體的實現類,然后組織、驗證 HTTP 請求信息,綁定動態數據,轉換數據形式,SSL 驗證簽名,調用后端 HTTP API(httpclient 等 API)執行實際請求,等待響應,失敗重試,轉換響應數據到 Java 類型等臟活累活都由這動態代理的實現類給包了。請求發送方調用這個接口時,實際上就是在調用這個干臟活累活的實現類。
5.Forest 的架構
architecture
我們講 HTTP 發送請求的過程分為前端部分和后端部分,Forest 本身是處理前端過程的框架,是對后端 HTTP API 框架的進一步封裝。
前端部分:
- Forest 配置:負責管理 HTTP 發送請求所需的配置。
- Forest 注解:用于定義 HTTP 發送請求的所有相關信息,一般定義在 interface 上和其方法上。
- 動態代理:用戶定義好的 HTTP 請求的interface將通過動態代理產生實際執行發送請求過程的代理類。
- 模板表達式:模板表達式可以嵌入在幾乎所有的 HTTP 請求參數定義中,它能夠將用戶通過參數或全局變量傳入的數據動態 綁定到 HTTP 請求信息中。
- 數據轉換:此模塊將字符串數據和JSON或XML形式數據進行互轉。目前 JSON 轉換器支持Jackson、Fastjson、Gson三種,XML 支持JAXB一種。
- 攔截器:用戶可以自定義攔截器,攔截指定的一個或一批請求的開始、成功返回數據、失敗、完成等生命周期中的各個環節,以插入自定義的邏輯進行處理。
- 過濾器:用于動態過濾和處理傳入 HTTP 請求的相關數據。
- SSL:Forest 支持單向和雙向驗證的 HTTPS 請求,此模塊用于處理 SSL 相關協議的內容。
后端部分:
后端為實際執行 HTTP 請求發送過程的第三方 HTTP API,目前支持okHttp3和httpclient兩種后端 API。
Spring Boot Starter Forest:提供對Spring Boot的支持
環境要求
Forest 1.0.x 和 Forest 1.1.x 基于 JDK 1.7, Forest 1.2.x及以上版本基于 JDK 1.8
三、Forest有哪些特性?
- 以Httpclient和OkHttp為后端框架
- 通過調用本地方法的方式去發送Http請求, 實現了業務邏輯與Http協議之間的解耦
- 因為針對第三方接口,所以不需要依賴Spring Cloud和任何注冊中心
- 支持所有請求方法:GET, HEAD, OPTIONS, TRACE, POST, DELETE, PUT, PATCH
- 支持文件上傳和下載
- 支持靈活的模板表達式
- 支持攔截器處理請求的各個生命周期
- 支持自定義注解
- 支持OAuth2驗證
- 支持過濾器來過濾傳入的數據
- 基于注解、配置化的方式定義Http請求
- 支持Spring和Springboot集成
- JSON字符串到Java對象的自動化解析
- XML文本到Java對象的自動化解析
- JSON、XML或其他類型轉換器可以隨意擴展和替換
- 支持JSON轉換框架: Fastjson, Jackson, Gson
- 支持JAXB形式的XML轉換
- 可以通過OnSuccess和OnError接口參數實現請求結果的回調
- 配置簡單,一般只需要@Request一個注解就能完成絕大多數請求的定義
- 支持異步請求調用
四、SpringBoot如何快速接入
在官方的文檔上明確介紹了有關Spring傳統項目如何接入Forest,這里我直接以SpringBoot為例,都是一個道理,無非對于SpringBoot提供的是xxx-spring-boot-starter以開頭的依賴forest-spring-boot-starter。
1. 第一步:添加Maven依賴
直接添加以下maven依賴即可:
- <dependency>
- <groupId>com.dtflys.forest</groupId>
- <artifactId>forest-spring-boot-starter</artifactId>
- <version>1.5.0</version>
- </dependency>
2. 第二步:創建一個interfacepackage
- package com.yoursite.client;
- import com.dtflys.forest.annotation.Request;
- import com.dtflys.forest.annotation.DataParam;
- public interface AmapClient {
- /**
- * 聰明的你一定看出來了@Get注解代表該方法專做GET請求
- * 在url中的${0}代表引用第一個參數,${1}引用第二個參數
- */
- @Get("http://ditu.amap.com/service/regeo?longitude=${0}&latitude=${1}")
- public Map getLocation(String longitude, String latitude);
- }
3. 第三步:掃描接口
在Spring Boot的配置類或者啟動類上加上@ForestScan注解,并在basePackages屬性里填上遠程接口的所在的包名:
- @SpringBootApplication
- @Configuration
- @ForestScan(basePackages = "com.yoursite.client")
- public class MyApplication {
- public static void main(String[] args) {
- SpringApplication.run(MyApplication.class, args);
- }
- }
4. 第四步:調用接口
- // 注入接口實例
- @Autowired
- private AmapClient amapClient;
- ...
- // 調用接口
- Map result = amapClient.getLocation("121.475078", "31.223577");
- System.out.println(result);
5. application.yml全局基本配置
- forest:
- bean-id: config0 # 在spring上下文中bean的id, 默認值為forestConfiguration
- backend: okhttp3 # 后端HTTP API:okhttp3
- max-connections: 1000 # 連接池最大連接數,默認值為500
- max-route-connections: 500 # 每個路由的最大連接數,默認值為500
- timeout: 3000 # 請求超時時間,單位為毫秒, 默認值為3000
- connect-timeout: 3000 # 連接超時時間,單位為毫秒, 默認值為2000
- retry-count: 1 # 請求失敗后重試次數,默認為0次不重試
- ssl-protocol: SSLv3 # 單向驗證的HTTPS的默認SSL協議,默認為SSLv3
- logEnabled: true # 打開或關閉日志,默認為true
- log-request: true # 打開/關閉Forest請求日志(默認為 true)
- log-response-status: true # 打開/關閉Forest響應狀態日志(默認為 true)
- log-response-content: true # 打開/關閉Forest響應內容日志(默認為 false
五、支持發送的請求類型
1. 請求類型:可支持(GET, POST, PUT, HEAD, OPTIONS, DELETE)使用POST方式
- public interface MyClient {
- /**
- * 通過 @Request 注解的 type 參數指定 HTTP 請求的方式。
- */
- @Request(
- url = "http://localhost:8080/hello",
- type = "POST"
- )
- String simplePost();
- /**
- * 使用 @Post 注解,可以去掉 type = "POST" 這行屬性
- */
- @Post("http://localhost:8080/hello")
- String simplePost();
- /**
- * 使用 @PostRequest 注解,和上面效果等價
- */
- @PostRequest("http://localhost:8080/hello")
- String simplePost();
- }
除了GET和POST,也可以指定成其他幾種HTTP 請求方式(PUT, HEAD, OPTIONS, DELETE)。
其中type屬性的大小寫不敏感,寫成POST和post效果相同。
- GET和POST大小寫不敏感
- // GET請求
- @Request(
- url = "http://localhost:8080/hello",
- type = "get"
- )
- String simpleGet();
- // POST請求
- @Request(
- url = "http://localhost:8080/hello",
- type = "post"
- )
- String simplePost();
- // PUT請求
- @Request(
- url = "http://localhost:8080/hello",
- type = "put"
- )
- String simplePut();
- // HEAD請求
- @Request(
- url = "http://localhost:8080/hello",
- type = "head"
- )
- String simpleHead();
- // Options請求
- @Request(
- url = "http://localhost:8080/hello",
- type = "options"
- )
- String simpleOptions();
- // Delete請求
- @Request(
- url = "http://localhost:8080/hello",
- type = "delete"
- )
- String simpleDelete();
另外,可以用@GetRequest, @PostRequest等注解代替@Request注解,這樣就可以省去寫type屬性的麻煩了。
- 例如xxxRequest等價于xxx
- // GET請求
- @Get("http://localhost:8080/hello")
- String simpleGet();
- // GET請求
- @GetRequest("http://localhost:8080/hello")
- String simpleGetRequest();
- // POST請求
- @Post("http://localhost:8080/hello")
- String simplePost();
- // POST請求
- @PostRequest("http://localhost:8080/hello")
- String simplePostRequest();
- // PUT請求
- @Put("http://localhost:8080/hello")
- String simplePut();
- // PUT請求
- @PutRequest("http://localhost:8080/hello")
- String simplePutRequest();
- // HEAD請求
- @HeadRequest("http://localhost:8080/hello")
- String simpleHead();
- // Options請求
- @Options("http://localhost:8080/hello")
- String simpleOptions();
- // Options請求
- @OptionsRequest("http://localhost:8080/hello")
- String simpleOptionsRequest();
- // Delete請求
- @Delete("http://localhost:8080/hello")
- String simpleDelete();
- // Delete請求
- @DeleteRequest("http://localhost:8080/hello")
- String simpleDeleteRequest();
如上所示,請求類型是不是更一目了然了,代碼也更短了。
@Get和@GetRequest兩個注解的效果是等價的,@Post和@PostRequest、@Put和@PutRequest等注解也是同理。
六、支持的數據發送格式
1. 發送JSON數據
- 將對象參數解析為JSON字符串,并放在請求的Body進行傳輸 :
- /**
- * 將對象參數解析為JSON字符串,并放在請求的Body進行傳輸
- */
- @Post("/register")
- public String registerUser(@JSONBody MyUser user);
- 將Map類型參數解析為JSON字符串,并放在請求的Body進行傳輸 :
- /**
- * 將Map類型參數解析為JSON字符串,并放在請求的Body進行傳輸
- */
- @Post("/test/json")
- public String postJsonMap(@JSONBody Map mapObj);
- 直接傳入一個JSON字符串,并放在請求的Body進行傳輸 :
- /**
- * 直接傳入一個JSON字符串,并放在請求的Body進行傳輸
- */
- @Post("/test/json")
- public String postJsonText(@JSONBody String jsonText);
2. 發送XML數據
- /**
- * 將一個通過JAXB注解修飾過的類型對象解析為XML字符串
- * 并放在請求的Body進行傳輸
- */
- @Post("/message")
- String sendXmlMessage(@XMLBody MyMessage message);
- /**
- * 直接傳入一個XML字符串,并放在請求的Body進行傳輸
- */
- @Post("/test/xml")
- String postXmlBodyString(@XMLBody String xml);
3. 文件上傳
- /**
- * 用@DataFile注解修飾要上傳的參數對象
- * OnProgress參數為監聽上傳進度的回調函數
- */
- @Post("/upload")
- Map upload(@DataFile("file") String filePath, OnProgress onProgress);
可以用一個方法加Lambda同時解決文件上傳和上傳的進度監聽
- Map result = myClient.upload("D:\\TestUpload\\xxx.jpg", progress -> {
- System.out.println("progress: " + Math.round(progress.getRate() * 100) + "%"); // 已上傳百分比
- if (progress.isDone()) { // 是否上傳完成
- System.out.println("-------- Upload Completed! --------");
- }
- });
4. 多文件批量上傳
- /**
- * 上傳Map包裝的文件列表,其中 ${_key} 代表Map中每一次迭代中的鍵值
- */
- @Post("/upload")
- ForestRequest<Map> uploadByteArrayMap(@DataFile(value = "file", fileName = "${_key}") Map<String, byte[]> byteArrayMap);
- /**
- * 上傳List包裝的文件列表,其中 ${_index} 代表每次迭代List的循環計數(從零開始計)
- */
- @Post("/upload")
- ForestRequest<Map> uploadByteArrayList(@DataFile(value = "file", fileName = "test-img-${_index}.jpg") List<byte[]> byteArrayList);
5. 文件下載
下載文件也是同樣的簡單
- /**
- * 在方法上加上@DownloadFile注解
- * dir屬性表示文件下載到哪個目錄
- * OnProgress參數為監聽上傳進度的回調函數
- * ${0}代表引用第一個參數
- */
- @Get("http://localhost:8080/images/xxx.jpg")
- @DownloadFile(dir = "${0}")
- File downloadFile(String dir, OnProgress onProgress);
調用下載接口以及監聽下載進度的代碼如下:
- File file = myClient.downloadFile("D:\\TestDownload", progress -> {
- System.out.println("progress: " + Math.round(progress.getRate() * 100) + "%"); // 已下載百分比
- if (progress.isDone()) { // 是否下載完成
- System.out.println("-------- Download Completed! --------");
- }
- });
6.基本簽名驗證
- @Post("/hello/user?username=${username}")
- @BasicAuth(username = "${username}", password = "bar")
- String send(@DataVariable("username") String username);
7. OAuth2.0
- @OAuth2(
- tokenUri = "/auth/oauth/token",
- clientId = "password",
- clientSecret = "xxxxx-yyyyy-zzzzz",
- grantType = OAuth2.GrantType.PASSWORD,
- scope = "any",
- username = "root",
- password = "xxxxxx"
- )
- @Get("/test/data")
- String getData();
等等特性,詳細文檔請看:http://forest.dtflyx.com/
七、詳細文檔請看:http://forest.dtflyx.com/