接口转发之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方式

 

 

 除了GETPOST,也可以指定成其他几种 HTTP 请求方式(PUTHEADOPTIONSDELETE)。

其中type属性的大小写不敏感,写成POSTpost效果相同

  // 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,以及很多请求的参数都是直接绑定在URLQuery部分上。

除此之外,也可以从外部动态传参的形式注入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给URLQuery部分传参也有多种方式,其中最简洁直白的就数字符串拼接了。


  /** *

    直接在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

POSTPUT等请求方法中,通常使用 HTTP 请求体进行传输数据。在 Forest 中有多种方式设置请求体数据。

通过data属性添加请求体

可以通过@Request注解的data属性把数据添加到请求体。需要注意的是只有当typePOSTPUTPATCH这类 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>

posted @ 2020-09-27 11:44  逍yao客  阅读(1033)  评论(0编辑  收藏  举报