Forest輕量級(jí) HTTP 客戶端
Forest是一個(gè)高層的、極簡(jiǎn)的輕量級(jí)HTTP調(diào)用API框架。
相比于直接使用Httpclient您不再用寫(xiě)一大堆重復(fù)的代碼了,而是像調(diào)用本地方法一樣去發(fā)送HTTP請(qǐng)求。
文檔和示例:
Forest有哪些特性?
- 以Httpclient和OkHttp為后端框架
- 通過(guò)調(diào)用本地方法的方式去發(fā)送Http請(qǐng)求, 實(shí)現(xiàn)了業(yè)務(wù)邏輯與Http協(xié)議之間的解耦
- 因?yàn)獒槍?duì)第三方接口,所以不需要依賴Spring Cloud和任何注冊(cè)中心
- 支持所有請(qǐng)求方法:GET, HEAD, OPTIONS, TRACE, POST, DELETE, PUT, PATCH
- 支持文件上傳和下載
- 支持靈活的模板表達(dá)式
- 支持?jǐn)r截器處理請(qǐng)求的各個(gè)生命周期
- 支持自定義注解
- 支持OAuth2驗(yàn)證
- 支持過(guò)濾器來(lái)過(guò)濾傳入的數(shù)據(jù)
- 基于注解、配置化的方式定義Http請(qǐng)求
- 支持Spring和Springboot集成
- JSON字符串到Java對(duì)象的自動(dòng)化解析
- XML文本到Java對(duì)象的自動(dòng)化解析
- JSON、XML或其他類型轉(zhuǎn)換器可以隨意擴(kuò)展和替換
- 支持JSON轉(zhuǎn)換框架: Fastjson, Jackson, Gson
- 支持JAXB形式的XML轉(zhuǎn)換
- 可以通過(guò)OnSuccess和OnError接口參數(shù)實(shí)現(xiàn)請(qǐng)求結(jié)果的回調(diào)
- 配置簡(jiǎn)單,一般只需要@Request一個(gè)注解就能完成絕大多數(shù)請(qǐng)求的定義
- 支持異步請(qǐng)求調(diào)用
極速開(kāi)始
以下例子基于Spring Boot
第一步:添加Maven依賴
直接添加以下maven依賴即可
<dependency> <groupId>com.dtflys.forest</groupId> <artifactId>forest-spring-boot-starter</artifactId> <version>1.5.0-RC7</version> </dependency>
第二步:創(chuàng)建一個(gè)interface
就以高德地圖API為栗子吧
package com.yoursite.client;
import com.dtflys.forest.annotation.Request;
import com.dtflys.forest.annotation.DataParam;
public interface AmapClient {
/**
* 聰明的你一定看出來(lái)了@Get注解代表該方法專做GET請(qǐng)求
* 在url中的${0}代表引用第一個(gè)參數(shù),${1}引用第二個(gè)參數(shù)
*/
@Get("http://ditu.amap.com/service/regeo?longitude=${0}&latitude=${1}")
Map getLocation(String longitude, String latitude);
}
第三步:掃描接口
在Spring Boot的配置類或者啟動(dòng)類上加上@ForestScan注解,并在basePackages屬性里填上遠(yuǎn)程接口的所在的包名
@SpringBootApplication @Configuration @ForestScan(basePackages = "com.yoursite.client") public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
第四步:調(diào)用接口
OK,我們可以愉快地調(diào)用接口了
// 注入接口實(shí)例 @Autowired private AmapClient amapClient; ... // 調(diào)用接口 Map result = amapClient.getLocation("121.475078", "31.223577"); System.out.println(result);
發(fā)送JSON數(shù)據(jù)
/** * 將對(duì)象參數(shù)解析為JSON字符串,并放在請(qǐng)求的Body進(jìn)行傳輸 */ @Post("/register") String registerUser(@JSONBody MyUser user); /** * 將Map類型參數(shù)解析為JSON字符串,并放在請(qǐng)求的Body進(jìn)行傳輸 */ @Post("/test/json") String postJsonMap(@JSONBody Map mapObj); /** * 直接傳入一個(gè)JSON字符串,并放在請(qǐng)求的Body進(jìn)行傳輸 */ @Post("/test/json") String postJsonText(@JSONBody String jsonText);
發(fā)送XML數(shù)據(jù)
/** * 將一個(gè)通過(guò)JAXB注解修飾過(guò)的類型對(duì)象解析為XML字符串 * 并放在請(qǐng)求的Body進(jìn)行傳輸 */ @Post("/message") String sendXmlMessage(@XMLBody MyMessage message); /** * 直接傳入一個(gè)XML字符串,并放在請(qǐng)求的Body進(jìn)行傳輸 */ @Post("/test/xml") String postXmlBodyString(@XMLBody String xml);
文件上傳
/** * 用@DataFile注解修飾要上傳的參數(shù)對(duì)象 * OnProgress參數(shù)為監(jiān)聽(tīng)上傳進(jìn)度的回調(diào)函數(shù) */ @Post("/upload") Map upload(@DataFile("file") String filePath, OnProgress onProgress);
可以用一個(gè)方法加Lambda同時(shí)解決文件上傳和上傳的進(jìn)度監(jiān)聽(tīng)
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! --------"); } });
多文件批量上傳
/** * 上傳Map包裝的文件列表,其中 ${_key} 代表Map中每一次迭代中的鍵值 */ @Post("/upload") ForestRequest<Map> uploadByteArrayMap(@DataFile(value = "file", fileName = "${_key}") Map<String, byte[]> byteArrayMap); /** * 上傳List包裝的文件列表,其中 ${_index} 代表每次迭代List的循環(huán)計(jì)數(shù)(從零開(kāi)始計(jì)) */ @Post("/upload") ForestRequest<Map> uploadByteArrayList(@DataFile(value = "file", fileName = "test-img-${_index}.jpg") List<byte[]> byteArrayList);
文件下載
下載文件也是同樣的簡(jiǎn)單
/** * 在方法上加上@DownloadFile注解 * dir屬性表示文件下載到哪個(gè)目錄 * OnProgress參數(shù)為監(jiān)聽(tīng)上傳進(jìn)度的回調(diào)函數(shù) * ${0}代表引用第一個(gè)參數(shù) */ @Get("http://localhost:8080/images/xxx.jpg") @DownloadFile(dir = "${0}") File downloadFile(String dir, OnProgress onProgress);
調(diào)用下載接口以及監(jiān)聽(tīng)下載進(jìn)度的代碼如下:
File file = myClient.downloadFile("D:\\TestDownload", progress -> { System.out.println("progress: " + Math.round(progress.getRate() * 100) + "%"); // 已下載百分比 if (progress.isDone()) { // 是否下載完成 System.out.println("-------- Download Completed! --------"); } });
基本簽名驗(yàn)證
@Post("/hello/user?username=${username}") @BasicAuth(username = "${username}", password = "bar") String send(@DataVariable("username") String username);
OAuth 2.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();
自定義注解
Forest允許您根據(jù)需要自行定義注解,不但讓您可以簡(jiǎn)單優(yōu)雅得解決各種需求,而且極大得擴(kuò)展了Forest的能力。
定義一個(gè)注解
/** * 用Forest自定義注解實(shí)現(xiàn)一個(gè)自定義的簽名加密注解 * 凡用此接口修飾的方法或接口,其對(duì)應(yīng)的所有請(qǐng)求都會(huì)執(zhí)行自定義的簽名加密過(guò)程 * 而自定義的簽名加密過(guò)程,由這里的@MethodLifeCycle注解指定的生命周期類進(jìn)行處理 * 可以將此注解用在接口類和方法上 */ @Documented /** 重點(diǎn): @MethodLifeCycle注解指定該注解的生命周期類*/ @MethodLifeCycle(MyAuthLifeCycle.class) @RequestAttributes @Retention(RetentionPolicy.RUNTIME) /** 指定該注解可用于類上或方法上 */ @Target({ElementType.TYPE, ElementType.METHOD}) public @interface MyAuth { /** * 自定義注解的屬性:用戶名 * 所有自定注解的屬性可以在生命周期類中被獲取到 */ String username(); /** * 自定義注解的屬性:密碼 * 所有自定注解的屬性可以在生命周期類中被獲取到 */ String password(); }
定義注解生命周期類
/** * MyAuthLifeCycle 為自定義的 @MyAuth 注解的生命周期類 * 因?yàn)?@MyAuth 是針對(duì)每個(gè)請(qǐng)求方法的,所以它實(shí)現(xiàn)自 MethodAnnotationLifeCycle 接口 * MethodAnnotationLifeCycle 接口帶有泛型參數(shù) * 第一個(gè)泛型參數(shù)是該生命周期類綁定的注解類型 * 第二個(gè)泛型參數(shù)為請(qǐng)求方法返回的數(shù)據(jù)類型,為了盡可能適應(yīng)多的不同方法的返回類型,這里使用 Object */ public class MyAuthLifeCycle implements MethodAnnotationLifeCycle<MyAuth, Object> { /** * 當(dāng)方法調(diào)用時(shí)調(diào)用此方法,此時(shí)還沒(méi)有執(zhí)行請(qǐng)求發(fā)送 * 次方法可以獲得請(qǐng)求對(duì)應(yīng)的方法調(diào)用信息,以及動(dòng)態(tài)傳入的方法調(diào)用參數(shù)列表 */ @Override public void onInvokeMethod(ForestRequest request, ForestMethod method, Object[] args) { System.out.println("Invoke Method '" + method.getMethodName() + "' Arguments: " + args); } /** * 發(fā)送請(qǐng)求前執(zhí)行此方法,同攔截器中的一樣 */ @Override public boolean beforeExecute(ForestRequest request) { // 通過(guò)getAttribute方法獲取自定義注解中的屬性值 // getAttribute第一個(gè)參數(shù)為request對(duì)象,第二個(gè)參數(shù)為自定義注解中的屬性名 String username = (String) getAttribute(request, "username"); String password = (String) getAttribute(request, "password"); // 使用Base64進(jìn)行加密 String basic = "MyAuth " + Base64Utils.encode("{" + username + ":" + password + "}"); // 調(diào)用addHeader方法將加密結(jié)構(gòu)加到請(qǐng)求頭MyAuthorization中 request.addHeader("MyAuthorization", basic); return true; } /** * 此方法在請(qǐng)求方法初始化的時(shí)候被調(diào)用 */ @Override public void onMethodInitialized(ForestMethod method, BasicAuth annotation) { System.out.println("Method '" + method.getMethodName() + "' Initialized, Arguments: " + args); } }
使用自定義的注解
/** * 在請(qǐng)求接口上加上自定義的 @MyAuth 注解 * 注解的參數(shù)可以是字符串模板,通過(guò)方法調(diào)用的時(shí)候動(dòng)態(tài)傳入 * 也可以是寫(xiě)死的字符串 */ @Get("/hello/user?username=${username}") @MyAuth(username = "${username}", password = "bar") String send(@DataVariable("username") String username);
詳細(xì)文檔請(qǐng)看:http://forest.dtflyx.com/
評(píng)論
圖片
表情
