1、引入依赖

  1. <dependency>
  2. <groupId>com.dtflys.forest</groupId>
  3. <artifactId>spring-boot-starter-forest</artifactId>
  4. <version>1.3.8</version>
  5. </dependency>

2、配置后端 HTTP API-在SpringBoot配置文件application.yml

目前 Forest 支持okhttp3httpclient两种后端 HTTP API,若不配置该属性,默认为okhttp3. 当然也可以改为httpclient

  1. forest:
  2. bean-id: config0 # 在spring上下文中bean的id, 默认值为forestConfiguration
  3. backend: okhttp3 # 后端HTTP API: okhttp3
  4. max-connections: 1000 # 连接池最大连接数,默认值为500
  5. max-route-connections: 500 # 每个路由的最大连接数,默认值为500
  6. timeout: 3000 # 请求超时时间,单位为毫秒, 默认值为3000
  7. connect-timeout: 3000 # 连接超时时间,单位为毫秒, 默认值为2000
  8. retry-count: 1 # 请求失败后重试次数,默认为0次不重试
  9. ssl-protocol: SSLv3 # 单向验证的HTTPS的默认SSL协议,默认为SSLv3
  10. logEnabled: true # 打开或关闭日志,默认为true

注意: 这里retry-count只是简单机械的请求失败后的重试次数,所以一般建议设置为0。 如果一定要多次重试,请一定要在保证服务端的幂等性的基础上进行重试,否则容易引发生产事故!

3、在SpringBoot中开启Forest的包扫描进行动态代理

在SpringBoot启动类添加注解 @ForestScan(basePackages = "com.fcant.service.forest")``

  1. @SpringBootApplication
  2. @ForestScan(basePackages = "com.example.demo.forest")
  3. public class DemoApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(DemoApplication.class, args);
  6. }
  7. }

4、请求接口构建-方法上面使用 @Request 注解定制请求

@Request 默认使用GET方式,且将请求响应的数据以String的方式返回给调用者。

  1. public interface MyClient {
  2. @Request(url = "http://localhost:5000/hello")
  3. String simpleRequest();
  4. }

@Request 注解的参数

参数 类型 说明
url String 请求的主机地址和端口,或者域名映射
headers 字符串数组
type String 请求类型,默认get,大小写不敏感
data 请求的数据体参数
contentType String 请求头数据类型
dataType String/JSON 接受的数据类型

type:支持的请求类型

通过@Request注解的type参数指定 HTTP 请求的方式。
除了GETPOST,也可以指定成其他几种 HTTP 请求方式(PUT, HEAD, OPTIONS, DELETE)。
其中type属性的大小写不敏感,写成POSTpost效果相同。

  1. public interface MyClient {
  2. // GET请求
  3. @Request(
  4. url = "http://localhost:5000/hello",
  5. type = "get"
  6. )
  7. String simpleGet();
  8. // POST请求
  9. @Request(
  10. url = "http://localhost:5000/hello",
  11. type = "post"
  12. )
  13. String simplePost();
  14. // PUT请求
  15. @Request(
  16. url = "http://localhost:5000/hello",
  17. type = "put"
  18. )
  19. String simplePut();
  20. // HEAD请求
  21. @Request(
  22. url = "http://localhost:5000/hello",
  23. type = "head"
  24. )
  25. String simpleHead();
  26. // Options请求
  27. @Request(
  28. url = "http://localhost:5000/hello",
  29. type = "options"
  30. )
  31. String simpleOptions();
  32. // Delete请求
  33. @Request(
  34. url = "http://localhost:5000/hello",
  35. type = "delete"
  36. )
  37. String simpleDelete();
  38. }

header:定义请求头

headers属性接受的是一个字符串数组,在接受多个请求头信息时以以下形式填入请求头,其中组数每一项都是一个字符串,每个字符串代表一个请求头。请求头的名称和值用:分割。

  1. {
  2. "请求头名称1: 请求头值1",
  3. "请求头名称2: 请求头值2",
  4. "请求头名称3: 请求头值3",
  5. ...
  6. }

使用示例

  1. public interface MyClient {
  2. @Request(
  3. url = "http://localhost:5000/hello/user",
  4. headers = {
  5. "Accept-Charset: utf-8",
  6. "Content-Type: text/plain"
  7. }
  8. )
  9. String multipleHeaders();
  10. }

实际产生的 HTTP 请求如下

  1. GET http://localhost:5000/hello/user
  2. HEADER:
  3. Accept-Charset: utf-8
  4. Content-Type: text/plain

data:请求体数据

通过@Request注解的data属性把数据添加到请求体。需要注意的是只有当typePOSTPUTPATCH这类 HTTP Method 时,data属性中的值才会绑定到请求体中,而GET请求在有些情况会绑定到url的参数中。
具体type属性和data属性数据绑定位置的具体关系如下表:

type data属性数据绑定位置 支持的contentType或Content-Type请求头
GET url参数部分 只有application/x-www-form-urlencoded
POST 请求体 任何contentType
PUT 请求体 任何contentType
PATCH 请求体 任何contentType
HEAD url参数部分 只有application/x-www-form-urlencoded
OPTIONS url参数部分 只有application/x-www-form-urlencoded
DELETE url参数部分 只有application/x-www-form-urlencoded
TRACE url参数部分 只有application/x-www-form-urlencoded

data 属性中进行数据绑定

  1. public interface MyClient {
  2. @Request(
  3. url = "http://localhost:5000/hello/user",
  4. type = "post",
  5. data = "username=${0}&password=${1}",
  6. headers = {"Accept:text/plan"}
  7. )
  8. String dataPost(String username, String password);
  9. }

dataType:接受的数据类型

Forest请求会自动将响应的返回数据反序列化成需要的数据类型。想要接受指定类型的数据需要完成两步操作:
第一步:定义dataType属性
dataType属性指定了该请求响应返回的数据类型,目前可选的数据类型有三种: text, json, xml
Forest会根据指定的dataType属性选择不同的反序列化方式。其中dataType的默认值为text,如果不指定其他数据类型,那么Forest就不会做任何形式的序列化,并以文本字符串的形式返回。

  1. /**
  2. * dataType为text或不填时,请求响应的数据将以文本字符串的形式返回回来
  3. */
  4. @Request(
  5. url = "http://localhost:8080/text/data",
  6. dataType = "text"
  7. )
  8. String getData();

若指定为jsonxml,那就告诉了Forest该请求的响应数据类型为JSON或XML形式的数据,就会以相应的形式进行反序列化。

  1. /**
  2. * dataType为json或xml时,Forest会进行相应的反序列化
  3. */
  4. @Request(
  5. url = "http://localhost:8080/text/data",
  6. dataType = "json"
  7. )
  8. Map getData();

注意: Forest需要指明返回类型(如User)的同时,也需要指明数据类型dataType为json或其他类型。

回调函数

在Forest中的回调函数使用单方法的接口定义,这样可以在 Java 8Kotlin 语言中方便使用 Lambda 表达式。
使用的时候只需在接口方法加入OnSuccess<T>类型或OnError类型的参数:

  1. @Request(
  2. url = "http://localhost:5000/hello/user",
  3. headers = {"Accept:text/plan"},
  4. data = "username=${username}"
  5. )
  6. String send(@DataVariable("username") String username, OnSuccess<String> onSuccess, OnError onError);

OnSuccess<T>在请求成功调用响应时会被调用,而OnError在失败或出现错误的时候被调用。
其中OnSuccess<T>的泛型参数T定义为请求响应返回结果的数据类型。

  1. myClient.send("foo", (String resText, ForestRequest request, ForestResponse response) -> {
  2. // 成功响应回调
  3. System.out.println(resText);
  4. },
  5. (ForestRuntimeException ex, ForestRequest request, ForestResponse response) -> {
  6. // 异常回调
  7. System.out.println(ex.getMessage());
  8. });

提示: 在异步请求中只能通OnSuccess<T>回调函数接或Future返回值接受数据。 而在同步请求中,OnSuccess<T>回调函数和任何类型的返回值都能接受到请求响应的数据。 OnError回调函数可以用于异常处理,一般在同步请求中使用try-catch也能达到同样的效果。

异步请求

在Forest使用异步请求,可以通过设置@Request注解的async属性为true实现,不设置或设置为false即为同步请求。

  1. @Request(
  2. url = "http://localhost:5000/hello/user?username=${0}",
  3. async = true,
  4. headers = {"Accept:text/plan"}
  5. )
  6. void asyncGet(String username OnSuccess<String> onSuccess);

一般情况下,异步请求都通过OnSuccess<T>回调函数来接受响应返回的数据,而不是通过接口方法的返回值,所以这里的返回值类型一般会定义为void

  1. // 异步执行
  2. myClient.asyncGet("foo", (result, request, response) -> {
  3. // 处理响应结果
  4. System.out.println(result);
  5. });

不习惯函数式的编程方式,也可以用Future<T>类型定义方法返回值的方式来接受响应数据。

  1. @Request(
  2. url = "http://localhost:5000/hello/user?username=foo",
  3. async = true,
  4. headers = {"Accept:text/plan"}
  5. )
  6. Future<String> asyncFuture();

获取Future的结果

  1. // 异步执行
  2. Future<String> future = myClient.asyncFuture();
  3. // 获取数据
  4. String result = future.get();

5、数据绑定

通过数据绑定参数化定制请求

A.参数序号绑定

可以使用${数字}的方式引用对应顺序的参数,其中${...}是模板表达式的语法形式。
序号所对应的参数在接口方法调用时传入的值,会被自动绑定到${数字}所在的位置。
:参数序号从0开始计数。
比如${0}表示的就是第一个参数,${1}表示的第二个参数,以此类推。

  1. @Request(
  2. url = "${0}/send?un=${1}&pw=${2}&da=${3}&sm=${4}",
  3. type = "get",
  4. dataType = "json"
  5. )
  6. public Map send(
  7. String base,
  8. String userName,
  9. String password,
  10. String phoneList,
  11. String content
  12. );

方法调用传递参数

  1. myClient.send("http://localhost:8080", "DT", "123456", "123888888", "Hahaha");

效果

  1. GET http://localhost:8080/send?un=DT&pw=123456&da=123888888&sm=Hahaha

B.通过注解 @DataParam 进行参数绑定

在接口方法的参数前加上@DataParam注解并在value属性中给予一个名词,就能实现参数绑定。

①参数绑定的位置

@DataParam注解修饰的参数数据的绑定位置较为灵活多变,它可以出现在请求url的参数部分,也可以出现在请求 Body 中。具体出现位置取决于由type属性定义的 HTTP Method,其绑定的具体位置如下表:

type @ParaParam注解绑定位置
GET url参数部分
POST 请求体
PUT 请求体
PATCH 请求体
HEAD url参数部分
OPTIONS url参数部分
DELETE url参数部分
TRACE url参数部分

GETHEAD等方法会将参数值直接绑定到 URL 的参数中。

  1. @Request(
  2. url = "${0}/send",
  3. type = "get",
  4. dataType = "json"
  5. )
  6. public Map send(
  7. String base,
  8. @DataParam("un") String userName,
  9. @DataParam("pw") String password,
  10. @DataParam("da") String phoneList,
  11. @DataParam("sm") String content
  12. );

方法调用

  1. myClient.send("http://localhost:8080", "DT", "123456", "123888888", "Hahaha");

效果

  1. GET http://localhost:8080/send?un=DT&pw=123456&da=123888888&sm=Hahaha

POSTPUT等方法会将参数值绑定到请求体中,同时在请求中的数据在默认情况下按x-www-form-urlencoded格式绑定。

  1. @Request(
  2. url = "http://localhost:5000/hello",
  3. type = "post",
  4. headers = {"Accept:text/plan"}
  5. )
  6. String send(@DataParam("username") String username, @DataParam("password") String password);

方法调用

  1. myClient.send("foo", "bar");

效果

  1. POST http://localhost:5000/hello
  2. HEADER:
  3. Accept: text/plan
  4. BODY:
  5. username=foo&password=bar

②数据绑定格式

若想参数值在请求中传输JSONXML等格式绑定,可以通过修改contentTypeContent-Type请求头来实现对应的数据格式, 具体数据格式和contentType属性的关系请参考下表:

contentType / Content-Type 数据格式
不设置 x-www-form-urlencoded表单格式
application/x-www-form-urlencoded x-www-form-urlencoded表单格式
application/json JSON格式
application/xml XML格式

若传输的 Body 数据是标准表单格式,就不用设置cotentType或请求头Content-Type

  1. public interface MyClient {
  2. @Request(
  3. url = "http://localhost:5000/hello/user",
  4. type = "post",
  5. headers = {"Accept:text/plan"}
  6. )
  7. String postBody(@DataParam("username") String username, @DataParam("password") String password);
  8. }

如果调用方代码如下所示:

  1. myClient.postBody("foo", "bar");

实际产生的 HTTP 请求如下:

  1. POST http://localhost:5000/hello/user
  2. HEADER:
  3. Accept: text/plan
  4. BODY:
  5. username=foo&password=bar

若要将 Body 中的数据内容转换成 JSON 格式,只要设置contentType属性为application/json即可

  1. public interface MyClient {
  2. @Request(
  3. url = "http://localhost:5000/hello/user",
  4. type = "post",
  5. contentType = "application/json"
  6. )
  7. String postBody(@DataParam("username") String username, @DataParam("password") String password);
  8. }

如果调用方代码如下所示:

  1. myClient.postBody("foo", "bar");

实际产生的 HTTP 请求如下:

  1. POST http://localhost:5000/hello/user
  2. HEADER:
  3. Content-Type: application/json
  4. BODY:
  5. {"username":"foo","password":"bar"}

或者不用contentType属性,使用请求头Content-Type: application/json效果相同

  1. public interface MyClient {
  2. @Request(
  3. url = "http://localhost:5000/hello/user",
  4. type = "post",
  5. headers = {"Content-Type: application/json"}
  6. )
  7. String postBody(@DataParam("username") String username, @DataParam("password") String password);
  8. }

C.@DataVariable 参数绑定

在接口方法中定义的参数前加上@DataVariable注解并value中输入一个名称,便可以实现参数的变量名绑定。
@DataVariable注解的value的值便是该参数在 Forest 请求中对应的变量名
意思就是在@Request的多个不同属性(url, headers, data)中通过${变量名}的模板表达式的语法形式引用之前在@DataVariable注解上定义的变量名,实际引用到的值就是调用该方法时传入该参数的实际值。
@DataVariable注解修饰的参数数据可以出现在请求的任意部分(url, header, data),具体在哪个部分取决于在哪里引用它。如果没有任何地方引用该变量名,该变量的值就不会出现在任何地方。

  1. @Request(
  2. url = "${base}/send?un=${un}&pw=${pw}&da=${da}&sm=${sm}",
  3. type = "get",
  4. dataType = "json"
  5. )
  6. public Map send(
  7. @DataVariable("base") String base,
  8. @DataVariable("un") String userName,
  9. @DataVariable("pw") String password,
  10. @DataVariable("da") String phoneList,
  11. @DataVariable("sm") String content
  12. );

如果调用方代码如下所示:

  1. myClient.send("http://localhost:8080", "DT", "123456", "123888888", "Hahaha");

实际产生的 HTTP 请求如下:

  1. GET http://localhost:8080/send?un=DT&pw=123456&da=123888888&sm=Hahaha

D.@DataObject 对象绑定

上面的提到的@DataParam绑定,以及在data属性中的绑定可以方便解决大部分数据传输要求。 但缺点是参数要一个一个写,如果有很多参数或属性会写的十分复杂,而且请求要传输一个十分复杂的对象就会变得非常无能为力。
所以为了解决这个问题,Forest引入了@DataObject注解针对对象进行数据绑定。
请看下面的例子:

  1. @Request(url = "http://localhost:5000/hello/user")
  2. String send(@DataObject User user);

如果调用请求接口send方法,并传入一个User类的对象,会得到以下结果:

  1. GET http://localhost:5000/hello/user?username=foo&password=bar

如上所示,@DataObject注解会将传入的对象拆成一个一个属性键值对放入urlquery参数部分。用这种方式,即使有十几二十个参数,接口的参数也只需传一个对象即可。
@DataObject绑定的数据不仅可以出现在url参数上,还可以出现在请求体中,不仅可以产生query参数的数据格式,也可以变成json格式或是xml格式。
具体数据绑定的出现位置请看下表:

type 数据绑定的位置
GET url参数部分
POST 请求体
PUT 请求体
PATCH 请求体
HEAD url参数部分
OPTIONS url参数部分
DELETE url参数部分
TRACE url参数部分

①绑定到请求体

绑定到请求体:

  1. @Request(
  2. url = "http://localhost:5000/hello/user",
  3. type = "post"
  4. )
  5. String send(@DataObject User user);

调用后产生的结果如下:

  1. POST http://localhost:5000/hello/user
  2. BODY:
  3. username=foo&password=bar

如上所示,User对象的数据出现在了Body中。这里出现的是x-www-form-urlencoded表单数据格式,是在没指定内容格式情况下的默认格式。
若要以其它形式(比如JSON)传输数据,需要做一些改动。

②绑定为JSON格式

要让@DataObject绑定的对象转换成JSON格式也非常简单,只要将contentType属性或Content-Type请求头指定为application/json便可。

  1. @Request(
  2. url = "http://localhost:5000/hello/user",
  3. contentType = "application/json",
  4. type = "post"
  5. )
  6. String send(@DataObject User user);

调用后产生的结果如下:

  1. POST http://localhost:5000/hello/user
  2. HEADER:
  3. Content-Type: application/json
  4. BODY:
  5. {"username": "foo", "password": "bar"}

③绑定为XML格式

@DataObject较为特殊,除了指定contentType属性或Content-Type请求头为application/xml外,还需要设置@DataObjectfilter属性为xml

  1. @Request(
  2. url = "http://localhost:5000/hello/user",
  3. contentType = "application/xml",
  4. type = "post"
  5. )
  6. String send(@DataObject(filter = "xml") User user);

此外,这里的User对象也要绑定JAXB注解:

  1. @XmlRootElement(name = "misc")
  2. public User {
  3. private String usrname;
  4. private String password;
  5. public String getUsername() {
  6. return username;
  7. }
  8. public void setUsername(String username) {
  9. this.username = username;
  10. }
  11. public String getPassword() {
  12. return password;
  13. }
  14. public void setPassword(String password) {
  15. this.password = password;
  16. }
  17. }

调用传入User对象后的结果如下:

  1. POST http://localhost:5000/hello/user
  2. HEADER:
  3. Content-Type: application/xml
  4. BODY:
  5. <misc><username>foo</username><password>bar</password></misc>

E.全局变量绑定

若已经在SpringBoot的application.yml或者application.propertise文件中定义好全局变量,那便可以直接在请求定义中绑定全局变量了。
若有全局变量:

  1. basetUrl: http://localhost:5050
  2. usrename: foo
  3. userpwd: bar
  4. phoneList: 123888888
  1. @Request(
  2. url = "${basetUrl}/send?un=${usrename}&pw=${userpwd}&da=${phoneList}&sm=${sm}",
  3. type = "get",
  4. dataType = "json"
  5. )
  6. Map testVar(@DataVariable("sm") String content);

如果调用方代码如下所示

  1. myClient.send("Xxxxxx");

实际产生的 HTTP 请求如下:

  1. GET http://localhost:5050/send?un=foo&pw=bar&da=123888888&sm=Xxxxxx