一、前言
最近在码云上扒了一波,发现了一个非常优秀的开源的轻量级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/