接口转发之forest框架
相信大家都很疑惑什么是forest?
Forest 是一个开源的 Java HTTP 客户端框架,它能够将 HTTP 的所有请求信息(包括 URL、Header 以及 Body 等信息)绑定到您自定义的 Interface 方法上,能够通过调用本地接口方法的方式发送 HTTP 请求。
那么再来讲一下为什么使用forest.
使用 Forest 就像使用类似 Dubbo 那样的 RPC 框架一样,只需要定义接口,调用接口即可,不必关心具体发送 HTTP 请求的细节。同时将 HTTP 请求信息与业务代码解耦,方便您统一管理大量 HTTP 的 URL、Header 等信息。而请求的调用方完全不必在意 HTTP 的具体内容,即使该 HTTP 请求信息发生变更,大多数情况也不需要修改调用发送请求的代码。
forest如何使用?
Forest 不需要您编写具体的 HTTP 调用过程,只需要定义一个业务接口,然后通过 Forest 注解将 HTTP 请求的信息添加到接口的方法上即可。请求发送方通过调用您定义的接口便能自动发送请求和接受请求的响应(接口转发)。
forest工作原理
Forest 会将您定义好的接口通过动态代理的方式生成一个具体的实现类,然后组织、验证 HTTP 请求信息,绑定动态数据,转换数据形式,SSL 验证签名,调用后端 HTTP API(httpclient 等 API)执行实际请求,等待响应,失败重试,转换响应数据到 Java 类型等脏活累活都由这动态代理的实现类给包了。 请求发送方调用这个接口时,实际上就是在调用这个干脏活累活的实现类。
对应的java版本
Forest 1.0.x 和 Forest 1.1.x 基于 JDK 1.7, Forest 1.2.x及以上版本基于 JDK 1.8
框架应用与安装
1.1在spring boot项目中安装
基于Spring Boot
,那只要添加下面一个 maven 依赖便可。
<dependency>
<groupId>com.dtflys.forest</groupId>
<artifactId>spring-boot-starter-forest</artifactId>
<version>1.4.11</version>
</dependency>
如果用的是Gradle:
compile group: 'com.dtflys.forest', name: 'spring-boot-starter-forest', version: '1.4.11'
1.2在非spring boot项目中安装
添加 Forest 核心包依赖
<dependency>
<groupId>com.dtflys.forest</groupId>
<artifactId>forest-core</artifactId>
<version>1.4.11</version>
</dependency>
如果用的是Gradle:
compile group: 'com.dtflys.forest', name: 'forest-core', version: '1.4.11'
完成以上步骤,接下来构建请求接口
在 Forest 依赖加入好之后,就可以构建 HTTP 请求的接口了。
在 Forest 中,所有的 HTTP 请求信息都要绑定到某一个接口的方法上,不需要编写具体的代码去发送请求。请求发送方通过调用事先定义好 HTTP 请求信息的接口方法,自动去执行 HTTP 发送请求的过程,其具体发送请求信息就是该方法对应绑定的 HTTP 请求信息。
简单的请求demo
创建一个interface
,并用@Request
注解修饰接口方法。
public interface MyClient {
@Request(url = "http://localhost:5000/hello")
String simpleRequest();
}
通过@Request
注解,将上面的MyClient
接口中的simpleRequest()
方法绑定了一个 HTTP 请求, 其 URL 为http://localhost:5000/hello
,并默认使用GET
方式,且将请求响应的数据以String
的方式返回给调用者。
复杂点的请求
public interface MyClient {
@Request( url = "http://localhost:5000/hello/user",
headers = "Accept: text/plain" )
String sendRequest(@DataParam("uname") String username);
}
上面的sendRequest
方法绑定的 HTTP 请求,定义了 URL 信息,以及把Accept:text/plain
加到了请求头中, 方法的参数String username
绑定了注解@DataParam("uname")
,它的作用是将调用者传入入参 username 时,自动将username
的值加入到 HTTP 的请求参数uname
中。
如果调用方代码如下所示:
MyClient myClient; ... myClient.sendRequest("foo");
这段调用所实际产生的 HTTP 请求如下:
GET http://localhost:5000/hello/user?uname=foo
HEADER:
Accept: text/plain
HTTP Method
使用POST
方式
除了GET
和POST
,也可以指定成其他几种 HTTP 请求方式(PUT
, HEAD
, OPTIONS
, DELETE
)。
其中type
属性的大小写不敏感,写成POST
和post
效果相同
// GET请求
@Request( url = "http://localhost:5000/hello",
type = "get" )
String simpleGet();
// POST请求
@Request( url = "http://localhost:5000/hello",
type = "post" )
String simplePost();
// PUT请求
@Request( url = "http://localhost:5000/hello",
type = "put" )
String simplePut();
// HEAD请求
@Request( url = "http://localhost:5000/hello",
type = "head" )
String simpleHead();
// Options请求
@Request( url = "http://localhost:5000/hello",
type = "options" )
String simpleOptions();
// Delete请求
@Request( url = "http://localhost:5000/hello",
type = "delete" )
String simpleDelete();
另外,可以用@GetRequest
, @PostRequest
等注解代替@Request
注解,这样就可以省去写type
属性的麻烦了。
// GET请求
@Get(url = "http://localhost:5000/hello")
String simpleGet();
// GET请求
@GetRequest(url = "http://localhost:5000/hello")
String simpleGetRequest();
// POST请求
@Post(url = "http://localhost:5000/hello")
String simplePost();
// POST请求
@PostRequest(url = "http://localhost:5000/hello")
String simplePostRequest();
// PUT请求
@Put(url = "http://localhost:5000/hello")
String simplePut();
// PUT请求
@PutRequest(url = "http://localhost:5000/hello")
String simplePutRequest();
// HEAD请求
@HeadRequest(url = "http://localhost:5000/hello")
String simpleHead();
// Options请求
@Options(url = "http://localhost:5000/hello")
String simpleOptions();
// Options请求
@OptionsRequest(url = "http://localhost:5000/hello")
String simpleOptionsRequest();
// Delete请求
@Delete(url = "http://localhost:5000/hello")
String simpleDelete();
// Delete请求
@DeleteRequest(url = "http://localhost:5000/hello")
String simpleDeleteRequest();
如上所示,请求类型是不是更一目了然了,代码也更短了。
@Get
和@GetRequest
两个注解的效果是等价的,@Post
和@PostRequest
、@Put
和@PutRequest
等注解也是同理。
需要注意的是,HEAD
请求类型没有对应的@Head
注解,只有@HeadRequest
注解。原因是容易和@Header
注解混淆
HTTP URL
HTTP请求可以没有请求头、请求体,但一定会有URL
,以及很多请求的参数都是直接绑定在URL
的Query
部分上。
除此之外,也可以从外部动态传参的形式注入URL
:
/** *
整个完整的URL都通过
@DataVariable 注解修饰的参数动态传入
*/
@Request(url = "${myURL}")
String send1(@DataVariable("myURL") String myURL);
/** *
通过参数转入的值值作为URL的一部分
*/
@Request(url = "http://${myURL}/abc")
String send2(@DataVariable("myURL") String myURL);
通过字符串传参
HTTP的URL
不光有协议名、域名、端口号等等基本信息,更为重要的是它能携带各种参数,称为Query
参数,它通常包含参数名和参数值两部分。
Forest给URL
的Query
部分传参也有多种方式,其中最简洁直白的就数字符串拼接了。
/**
*
直接在url字符串的问号后面部分直接写上 参数名=参数值 的形式
* 等号后面的参数值部分可以用 ${变量名} 这种字符串模板的形式替代
* 在发送请求时会动态拼接成一个完整的URL
*/
@Request(url = "http://${myURL}/abc?a=${a}&b=${b}&id=0")
String send2(@DataVariable("myURL") String myURL,@DataVariable("a") String a, @DataVariable("b") String b);
通过@query注解
但把所有Query
参数直接写在url
属性的字符串里面是不是也太简单粗暴了,有没有优雅点的方式?有的。
/**
* 使用 @Query 注解,可以直接将该注解修饰的参数动态绑定到请求url中
* 注解的 value 值即代表它在url的Query部分的参数名
*/
@Request(url = "http://${myURL}/abc?id=0")
String send2(@DataVariable("myURL"),@Query("a") String a, @Query("b") String b);
@Query 与 @DataParam 不同,@Query 注解修饰的参数一定会出现在 URL 中,而 @DataParam 修饰的参数则要视情况而定。
若是要传的URL
参数太多了呢?难道要我在方法上定义十几二十个@Query
修饰的参数?那也太不友好了。所以,Forest提供了解决办法。
/**
* 使用 @Query 注解,可以修饰 Map 类型的参数
* 很自然的,Map 的 Key 将作为 URL 的参数名, Value 将作为 URL 的参数值
* 这时候 @Query 注解不定义名称
*/
@Get(url = "http://${myURL}/abc?id=0")
String send2(@DataVariable("myURL"),@Query Map<String, Object> map);
/**
* @Query 注解也可以修饰自定义类型的对象参数
* 依据对象类的 Getter 和 Setter 的规则取出属性
* 其属性名为 URL 参数名,属性值为 URL 参数值
* 这时候 @Query 注解不定义名称
*/
@Get(url = "http://${myURL}/abc?id=0")
String send2(@DataVariable("myURL"),@Query UserInfo user);
瞬间简洁了很多,但是用@query注解也有需要注意的地方
注意:
(1) 需要单个单个定义 参数名=参数值 的时候,@Query注解的value值一定要有,比如 @Query("name") String name
(2) 需要绑定对象的时候,@Query注解的value值一定要空着,比如 @Query User user 或 @Query Map map
HTTP Header
我们已经知道了可以通过@Request
注解的headers
属性设置一条 HTTP 请求头。
现在我们来看看怎样添加多条请求头
通过headers属性
其中headers
属性接受的是一个字符串数组,在接受多个请求头信息时以以下形式填入请求头:
{
"请求头名称1: 请求头值1",
"请求头名称2: 请求头值2",
"请求头名称3: 请求头值3",
... }
其中组数每一项都是一个字符串,每个字符串代表一个请求头。请求头的名称和值用:
分割。
具体代码请看如下示例:
public interface MyClient {
@Request( url = "http://localhost:5000/hello/user",
headers = {
"Accept-Charset: utf-8",
"Content-Type: text/plain"
} )
String multipleHeaders();
}
该接口调用后所实际产生的 HTTP 请求如下:
GET http://localhost:5000/hello/user
HEADER:
Accept-Charset: utf-8
Content-Type: text/plain
如果要每次请求传入不同的请求头内容,可以在headers
属性的请求头定义中加入数据绑定
。
public interface MyClient {
@Request( url = "http://localhost:5000/hello/user",
headers = { "Accept-Charset: ${encoding}",
"Content-Type: text/plain" } )
String bindingHeader(@DataVariable("encoding") String encoding);
}
如果调用方代码如下所示:
myClient.bindingHeader("gbk");
这段调用所实际产生的 HTTP 请求如下:
GET http://localhost:5000/hello/user
HEADER:
Accept-Charset: gbk
Content-Type: text/plain
通过@header注解邦定请求头
大家都已经了解通过 headers
属性设置请求头的方法了。不过这种方式虽然直观,但如要没通过参数传入到请求头中就显得比较啰嗦了。
所以Forest还提供了 @Header
注解来帮助您把方法的参数直接绑定到请求头中。
/**
* 使用 @Header 注解将参数绑定到请求头上
* @Header 注解的 value 指为请求头的名称,参数值为请求头的值
* @Header("Accept") String accept将字符串类型参数绑定到请求头 Accept 上
* @Header("accessToken") String accessToken将字符串类型参数绑定到请求头 accessToken 上
*/
@Post(url = "http://localhost:${port}/hello/user?username=foo")
void postUser(@Header("Accept") String accept, @Header("accessToken") String accessToken);
如果有很多很多的请求头要通过参数形式传入,需要定义很多很多参数吗?当然也不用!
/**
* 使用 @Header 注解可以修饰 Map 类型的参数
* Map 的 Key 指为请求头的名称,Value 为请求头的值
* 通过此方式,可以将 Map 中所有的键值对批量地绑定到请求头中
*/
@Post(url = "http://localhost:${port}/hello/user?username=foo")
void headHelloUser(@Header Map<String, Object> headerMap);
/**
* 使用 @Header 注解可以修饰自定义类型的对象参数
* 依据对象类的 Getter 和 Setter 的规则取出属性
* 其属性名为 URL 请求头的名称,属性值为请求头的值
* 以此方式,将一个对象中的所有属性批量地绑定到请求头中
*/
@Post(url = "http://localhost:${port}/hello/user?username=foo")
void headHelloUser(@Header MyHeaderInfo headersInfo);
注意:
(1) 需要单个单个定义请求头的时候,@Header注解的value值一定要有,比如 @Header("Content-Type") String contentType
(2) 需要绑定对象的时候,@Header注解的value值一定要空着,比如 @Header MyHeaders headers 或 @Header Map headerMap
HTTP Body
在POST
和PUT
等请求方法中,通常使用 HTTP 请求体进行传输数据。在 Forest 中有多种方式设置请求体数据。
通过data属性添加请求体
可以通过@Request
注解的data
属性把数据添加到请求体。需要注意的是只有当type
为POST
、PUT
、PATCH
这类 HTTP Method 时,data
属性中的值才会绑定到请求体中,而GET
请求在有些情况会绑定到url
的参数中。
具体type
属性和data
属性数据绑定位置的具体关系如下表:
data
属性在POST
请求中绑定请求体:
public interface MyClient {
@Request( url = "http://localhost:5000/hello/user",
type = "post", data = "username=foo&password=bar",
headers = {"Accept:text/plain"} )
String dataPost(); }
该接口调用后所实际产生的 HTTP 请求如下:
POST
http://localhost:5000/hello/user
HEADER:
Accept:text/plain
BODY:
username=foo&password=bar
在data
属性中进行数据绑定:
public interface MyClient {
/**
* 这里 data 属性中设置的字符串内容会绑定到请求体中
* 其中 ${0} 和 ${1} 为参数序号绑定,会将序号对应的参数绑定到字符串中对应的位置
* ${0} 会替换为 username 的值,${1} 会替换为 password 的值
*/
@Request( url = "http://localhost:5000/hello/user",
type = "post",
data = "username=${0}&password=${1}",
headers = {"Accept:text/plain"} )
String dataPost(String username, String password); }
如果调用方代码如下所示:
myClient.dataPost("foo", "bar");
实际产生的 HTTP 请求如下:
POST http://localhost:5000/hello/user
HEADER:
Accept: text/plain
BODY:
username=foo&password=bar
也可以直接把 JSON 数据加入到请求体中,其中header
设置为Content-Type: application/json
public interface MyClient {
@Request( url = "http://localhost:5000/hello/user",
type = "post",
data = "{\"username\": \"${0}\", \"password\": \"${1}\"}",
headers = {"Content-Type: application/json"} )
String postJson(String username, String password); }
如果调用方代码如下所示:
myClient.postJson("foo", "bar");
实际产生的 HTTP 请求如下:
POST
http://localhost:5000/hello/user
HEADER:
Content-Type: application/json
BODY: {"username": "foo", "password": "bar"}
把 XML 数据加入到请求体中,其中header
设置为Content-Type: application/json
public interface MyClient {
@Request( url = "http://localhost:5000/hello/user",
type = "post",
data = "<misc><username>${0}</username><password>${1}</password></misc>",
headers = {"Content-Type: application/xml"} )
String postXml(String username, String password);
}
如果调用方代码如下所示:
myClient.postXml("foo", "bar");
实际产生的 HTTP 请求如下:
POST http://localhost:5000/hello/user
HEADER: Content-Type: application/xml
BODY: <misc><username>foo</username><password>bar</password></misc>