Forest详细介绍

news/2024/12/29 7:36:00/

文章目录

    • 前言
    • Forest介绍
      • 为什么使用 Forest?
      • Forest 如何使用?
      • Forest 的工作原理
      • Forest 的架构
    • Forest的使用
      • 依赖导入
      • 配置yml
      • 请求
      • 请求方法
      • URL 参数
      • 数据转换
        • Content-Type 请求头
        • 请求体类型
        • Encoder
      • 拦截器(常用)
        • 一、构建拦截器
        • 二、拦截器与 Spring 集成
      • 注解说明:
        • @Body 注解
        • @JSONBody注解修饰对象(常用)
        • @XMLBody注解修饰对象
        • @BaseRequest 注解(常用)
        • @Success 注解
        • `@Retry`注解
      • 文档和示例:

前言

这段时间我们公司在开发一个商城,我负责的是产品中心,需求有以下3点:

  1. 创建时需要同步到erp
  2. 同步时需要异步
  3. 同步失败时重试,3次
  4. 3次都失败时推送钉钉给对应的开发人员

其实1、2点都很容易实现,使用异步线程池,或者消息中间件等等都可以实现,第3点的话可以有很多实现方式, 可以在表中加个同步次数的字段,用定时任务去扫描,达到三次就发送,所以说实现的方式有很多种,就看你的选择,而我的选择是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 类型等脏活累活都由这动态代理的实现类给包了。 请求发送方调用这个接口时,实际上就是在调用这个干脏活累活的实现类。

Forest 的架构

在这里插入图片描述

Forest的使用

forest有两种使用方式,一种是声明式接口,也就是我们熟悉的注解方式,另一种是编程式接口,本篇文章只介绍声明式使用方式;

依赖导入

<dependency><groupId>com.dtflys.forest</groupId><artifactId>forest-spring-boot-starter</artifactId><version>1.5.26</version>
</dependency>

配置yml

forest:backend: okhttp3             # 目前 Forest 支持okhttp3和httpclient两种后端 HTTP API,若不配								置该属性,默认为okhttp3 当然,您也可以改为httpclientmax-connections: 1000        # 连接池最大连接数(默认为 500)max-route-connections: 500   # 每个路由的最大连接数(默认为 500)max-request-queue-size: 100  # [自v1.5.22版本起可用] 最大请求等待队列大小max-async-thread-size: 300   # [自v1.5.21版本起可用] 最大异步线程数max-async-queue-size: 16     # [自v1.5.22版本起可用] 最大异步线程池队列大小timeout: 3000                # [已不推荐使用] 请求超时时间,单位为毫秒(默认为 3000)connect-timeout: 3000        # 连接超时时间,单位为毫秒(默认为 timeout)read-timeout: 3000           # 数据读取超时时间,单位为毫秒(默认为 timeout)max-retry-count: 0           # 请求失败后重试次数(默认为 0 次不重试)ssl-protocol: TLS            # 单向验证的HTTPS的默认TLS协议(默认为 TLS)log-enabled: true            # 打开或关闭日志(默认为 true)log-request: true            # 打开/关闭Forest请求日志(默认为 true)log-response-status: true    # 打开/关闭Forest响应状态日志(默认为 true)log-response-content: true   # 打开/关闭Forest响应内容日志(默认为 false)

请求

请求一、

创建一个interface,并用@Request注解修饰接口方法。

public interface MyClient {@Request("http://localhost:8080/hello")String simpleRequest();}

通过@Request注解,将上面的MyClient接口中的simpleRequest()方法绑定了一个 HTTP 请求, 其 URL 为http://localhost:8080/hello ,并默认使用GET方式,且将请求响应的数据以String的方式返回给调用者。

请求二、

public interface MyClient {@Request(url = "http://localhost:8080/hello/user",headers = "Accept: text/plain")String sendRequest(@Query("uname") String username);
}

上面的sendRequest方法绑定的 HTTP 请求,定义了 URL 信息,以及把Accept:text/plain加到了请求头中,多个用逗号隔开, 方法的参数String username绑定了注解@Query("uname"),它的作用是将调用者传入入参 username 时,自动将username的值加入到 HTTP 的请求参数uname中。

请求方法

Forest 使用不同的请求注解来标识某个接口方法来进行发送不同类型的请求,其支持的HTTP方法如下表所示:

HTTP 请求方法请求注解描述
GET@Get@GetRequest获取资源
POST@Post@PostRequest传输实体文本
PUT@Put@PutRequest上传资源
HEAD@HeadRequest获取报文首部
DELETE@Delete@DeleteRequest删除资源
OPTIONS@Options@OptionsRequest询问支持的方法
TRACE@Trace@TraceRequest追踪路径
PATCH@Patch@PatchRequest更新资源的某一部分
不定方法@Request可动态传入HTTP方法

URL 参数

可通过{参数序号}来动态 获取参数,也可以在参数中使用@Var注解来标明变量名配合{变量名来获取}

/*** 整个完整的URL都通过参数传入* {0}代表引用第一个参数*/
@Get("{0}")
String send1(String myURL);/*** 整个完整的URL都通过 @Var 注解修饰的参数动态传入*/
@Get("{myURL}")
String send2(@Var("myURL") String myURL);/*** 通过参数转入的值作为URL的一部分*/
@Get("http://{myURL}/abc")
String send3(@Var("myURL") String myURL);/*** 参数转入的值可以作为URL的任意一部分*/
@Get("http://localhost:8080/test/{myURL}?a=1&b=2")
String send4(@Var("myURL") String myURL);

数据转换

几乎所有数据格式的转换都包含序列化和反序列化,Forest的数据转换同样如此

序列化是指,将原始的 Java 类型数据对象转化为 HTTP 请求想要发送的数据格式(如:JSONXMLProtobuf

Content-Type 请求头

Forest中对数据进行序列化可以通过指定contentType属性或Content-Type头指定内容格式

@Post(url = "http://localhost:8080/hello/user",contentType = "application/json"    // 指定contentType为application/json
)
String postJson(@Body MyUser user);   // 自动将user对象序列化为JSON格式

同理,指定为application/xml会将参数序列化为XML格式,text/plain则为文本,默认的application/x-www-form-urlencoded则为表格格式。

请求体类型

或者,也可以通过@BodyType注解指定type属性

// 自动将user对象序列化为JSON格式
// 但此方式不会在请求中带有 Content-Type 请求头
@Post("http://localhost:8080/hello/user")
@BodyType("json")
String postJson(@Body MyUser user);

Encoder

// 指定仅仅使用 Jackson 转换器来序列化数据
@Post("http://localhost:8080/hello/user")
@BodyType(type = "json", encoder = ForestJacksonConverter.class)
String postJson(@Body MyUser user);

提示

在方法不指定 Encoder 的默认情况下,会去找接口上有没有设置 Encoder,如接口上也没有则使用全局的转换器为改方法请求的 Encoder

拦截器(常用)

用过Spring MVC的朋友一定对Spring的拦截器并不陌生,Forest也同样支持针对Forest请求的拦截器。

一、构建拦截器

定义一个拦截器需要实现com.dtflys.forest.interceptor.Interceptor接口

public class SimpleInterceptor<T> implements Interceptor<T> {private final static Logger log = LoggerFactory.getLogger(SimpleInterceptor.class);/*** 该方法在被调用时,并在beforeExecute前被调用 * @Param request Forest请求对象* @Param args 方法被调用时传入的参数数组 */@Overridepublic void onInvokeMethod(ForestRequest req, ForestMethod method, Object[] args) {log.info("on invoke method");// req 为Forest请求对象,即 ForestRequest 类实例// method 为Forest方法对象,即 ForestMethod 类实例// addAttribute作用是添加和Forest请求对象以及该拦截器绑定的属性addAttribute(req, "A", "value1");addAttribute(req, "B", "value2");}/*** 该方法在请求发送之前被调用, 若返回false则不会继续发送请求* @Param request Forest请求对象*/@Overridepublic boolean beforeExecute(ForestRequest req) {log.info("invoke Simple beforeExecute");// 执行在发送请求之前处理的代码req.addHeader("accessToken", "11111111");  // 添加Headerreq.addQuery("username", "foo");  // 添加URL的Query参数return true;  // 继续执行请求返回true}/*** 该方法在请求成功响应时被调用*/@Overridepublic void onSuccess(T data, ForestRequest req, ForestResponse res) {log.info("invoke Simple onSuccess");// 执行成功接收响应后处理的代码int status = res.getStatusCode(); // 获取请求响应状态码String content = res.getContent(); // 获取请求的响应内容String result = (String)data;  // data参数是方法返回类型对应的返回数据结果,注意需要视情况修改对应的类型否则有可能出现类转型异常result = res.getResult(); // getResult()也可以获取返回的数据结果response.setResult("修改后的结果: " + result);  // 可以修改请求响应的返回数据结果// 使用getAttributeAsString取出属性,这里只能取到与该Forest请求对象,以及该拦截器绑定的属性String attrValue1 = getAttributeAsString(req, "A1");}/*** 该方法在请求发送失败时被调用*/@Overridepublic void onError(ForestRuntimeException ex, ForestRequest req, ForestResponse res) {log.info("invoke Simple onError");// 执行发送请求失败后处理的代码int status = res.getStatusCode(); // 获取请求响应状态码String content = res.getContent(); // 获取请求的响应内容String result = res.getResult(); // 获取方法返回类型对应的返回数据结果}/*** 该方法在请求发送之后被调用*/@Overridepublic void afterExecute(ForestRequest req, ForestResponse res) {log.info("invoke Simple afterExecute");// 执行在发送请求之后处理的代码int status = res.getStatusCode(); // 获取请求响应状态码String content = res.getContent(); // 获取请求的响应内容String result = res.getResult(); // 获取方法返回类型对应的最终数据结果}
}

Interceptor接口带有一个泛型参数,其表示的是请求响应后返回的数据类型。 Interceptor即代表返回的数据类型为 String

在拦截器的方法参数中基本都有 ForestRequest 类对象,即Forest请求对象,Forest的绝大部分操作都是围绕请求对象所作的工作。

二、拦截器与 Spring 集成

若我要在拦截器中注入 Spring 的 Bean 改如何做?


/*** 在拦截器的类上加上@Component注解,并保证它能被Spring扫描到*/
@Component
public class SimpleInterceptor implements Interceptor<String> {// 如此便能直接注入Spring上下文中所有的Bean了@Resouceprivate UserService userService;... ...
}

注解说明:

@Body 注解

使用@Body注解修饰参数的方式,将传入参数的数据绑定到 HTTP 请求体中。

/*** 默认body格式为 application/x-www-form-urlencoded,即以表单形式序列化数据*/
@Post("http://localhost:8080/user")
String sendPost(@Body("username") String username,  @Body("password") String password);

@JSONBody注解修饰对象(常用)

发送JSON非常简单,只要用@JSONBody注解修饰相关参数就可以了,该注解自1.5.0-RC1版本起可以使用。 使用@JSONBody注解的同时就可以省略 contentType = "application/json"属性设置。

/*** 被@JSONBody注解修饰的参数会根据其类型被自定解析为JSON字符串* 使用@JSONBody注解时可以省略 contentType = "application/json"属性设置*/
@Post("http://localhost:8080/hello/user")
String helloUser(@JSONBody User user);

@XMLBody注解修饰对象

发送XML也非常简单,只要用@XMLBody注解修饰相关参数就可以了,该注解自1.5.0-RC1版本起可以使用。

/*** 被@JSONBody注解修饰的参数会根据其类型被自定解析为XML字符串* 其修饰的参数类型必须支持JAXB,可以使用JAXB的注解进行修饰* 使用@XMLBody注解时可以省略 contentType = "application/xml"属性设置*/
@Post("http://localhost:8080/hello/user")
String sendXmlMessage(@XMLBody User user);

@BaseRequest 注解(常用)

@BaseRequest注解定义在接口类上,在@BaseRequest上定义的属性会被分配到该接口中每一个方法上,但方法上定义的请求属性会覆盖@BaseRequest上重复定义的内容。 因此可以认为@BaseRequest上定义的属性内容是所在接口中所有请求的默认属性。

/*** @BaseRequest 为配置接口层级请求信息的注解* 其属性会成为该接口下所有请求的默认属性* 但可以被方法上定义的属性所覆盖*/
@BaseRequest(baseURL = "http://localhost:8080",     // 默认域名headers = {"Accept:text/plain"                // 默认请求头},sslProtocol = "TLS"                    // 默认单向SSL协议
)
public interface MyClient {// 方法的URL不必再写域名部分@Get("/hello/user")String send1(@Query("username") String username);// 若方法的URL是完整包含http://开头的,那么会以方法的URL中域名为准,不会被接口层级中的baseURL属性覆盖@Get("http://www.xxx.com/hello/user")String send2(@Query("username") String username);@Get(url = "/hello/user",headers = {"Accept:application/json"      // 覆盖接口层级配置的请求头信息})     String send3(@Query("username") String username);}

@BaseRequest注解中的所有字符串属性都可以通过模板表达式{}引用全局变量或方法中的参数。

/** * 若全局变量中已定义 baseUrl 和 accept,* 便会将全局变量中的值绑定到 @BaseRequest 的属性中*/
@BaseRequest(baseURL = "${baseUrl}",     // 默认域名headers = {"Accept:${accept}"      // 默认请求头}
)
public interface MyClient {// 方法的URL的域名将会引用全局变量中定义的 baseUrl@Get("/hello/user")     String send1(@Query("username") String username);// @BaseRequest 中的属性亦可以引用方法中的绑定变量名的参数@Get("/hello/user")String send2(@Var("baseUrl") String baseUrl);}

@Success 注解

Forest 提供了默认的请求成功/失败条件,其逻辑如下:

  1. 判断是否在发送和等待响应的过程中出现异常,如: 网络连接错误、超时等
  2. 在取得响应结果后,判断其响应状态码是否在正常范围内 (100 ~ 399)

以上两条判断条件如有一条不满足,则就判定为请求失败,否则为成功。

默认的判断条件可以满足绝大部分场景的需要,也比较符合HTTP协议标准的规范,但也存在一些特殊场景,并不以HTTP标准为判断逻辑,这时候就需要用户进行自定义的请求成功/失败条件的判断了.

第一步:先要定义 SuccessWhen 接口的实现类

// 自定义成功/失败条件实现类
// 需要实现 SuccessWhen 接口
public class MySuccessCondition implements SuccessWhen {/*** 请求成功条件* @param req Forest请求对象* @param res Forest响应对象* @return 是否成功,true: 请求成功,false: 请求失败*/@Overridepublic boolean successWhen(ForestRequest req, ForestResponse res) {// req 为Forest请求对象,即 ForestRequest 类实例// res 为Forest响应对象,即 ForestResponse 类实例// 返回值为 ture 则表示请求成功,false 表示请求失败return res.noException() &&   // 请求过程没有异常res.statusOk() &&     // 并且状态码在 100 ~ 399 范围内res.statusIsNot(203); // 但不能是 203// 当然在这里也可以写其它条件,比如 通过 res.getResult() 或 res.getContent() 获取业务数据// 再更具业务数据判断是否成功}
}

特别注意

successWhen方法的逻辑代码中,千万不能调用 res.isSuccess()!res.isError() 进行判断

不然会引起死循环

第二步,挂上 @Success 注解

public interface MyClient {/*** 挂上了 @Success 注解* <p>该方法的请求是否成功* <p>以自定义成功条件类 MySuccessCondition 的判断方法为准* * @return 请求响应结果*/@Get("/")@Success(condition = MySuccessCondition.class)String sendData();
}

若调用sendData()方法后,返回的状态码为 203, 就会被认为是请求失败,如果设置了重试次数大于0,就会去执行重试任务。 若没有重试次数可用,则进入 onError 请求失败流程

@Retry注解

重试机制

public interface MyClient {// maxRetryCount 为最大重试次数,默认为 0 次// maxRetryInterval 为最大重试时间间隔, 单位为毫秒,默认为 0 毫秒@Get("/")@Retry(maxRetryCount = "3", maxRetryInterval = "10")String sendData();
}

这里的@Retry注解设置了两个属性:

  • maxRetryCount: 请求的最大重试次数,当重试次数大于该属性,将停止触发请求重试流程,默认为0次,即不会触发重试
  • maxRetryInterval: 请求的最大重试时间间隔,单位为毫秒,默认为0毫秒,即没有时间间隔

当调用该接口的sendData()方法后,若请求失败就会不断进行重试,直到不满足重试条件为止 (即请求成功,或达到最大请求次数限制)

文档和示例:

  • 项目主页
  • 中文文档
  • JavaDoc
  • Demo工程

http://www.ppmy.cn/news/310589.html

相关文章

速收藏!实拍你的蚂蚁森林,张张都是高清美图壁纸

你们过去在蚂蚁森林通过步行、线下支付、生活缴费、骑共享单车等各种低碳行为&#xff0c;以及起早贪黑抛亲弃友好不容易养大的树&#xff0c;终于长大了&#xff01; 2017年秋天&#xff0c;蚂蚁森林组织了一个蚂蚁森林小分队去了一趟蚂蚁森林&#xff0c;给那些迫切想要看看自…

树和森林(Tree and Forest)

树型结构是一种重要的非线性数据结构。树型结构在客观世界广泛存在&#xff0c;如组织关系可用树来表示。树在计算机领域也有广泛应用&#xff0c;如在编译程序时&#xff0c;可用树来表示源程序的语法结构(语法树)。又如在数据库系统中&#xff0c;使用树型结构存储索引等信息…

森林攻略

1.存档攻略 C盘/ Users / Administrator / AppData / LocalLow / SKS / The forest / 一列数字 你保存的存档&#xff0c;就在那个名称全是数字的文件夹中&#xff0c;只需把其中的存档文件保存在U盘或网盘中&#xff0c;下次玩时&#xff0c;把保存的存档文件放到存档文件夹…

森林图

森林图 以统计指标和统计分析方法为基础&#xff0c;用数值计算绘制出的图形&#xff0c;通常是在平面直角坐标系中&#xff0c;以一条垂直的无效 线&#xff08;0或者1&#xff09;为中心&#xff0c;用平衡于x轴的多条线段描述每个组指标的中值和可信区间&#xff0c;最后一…

高效与灵活相结合:Verilog编写的跨时钟域循环缓冲控制器解析

目录 一、硬件循环缓冲器控制器 二、Verilog编写的循环缓冲控制器 三、使用方法 一、硬件循环缓冲器控制器 1.1 介绍 本文将介绍一种用Verilog编写的循环缓冲控制器&#xff0c;这种控制器主要用于FPGA设计中的数据流缓冲。 项目下载​​​​​​​ 1.2 缓冲区概念 循环…

vue3---模板引用 nextTick

目录 模板引用--ref 访问模板引用 v-for 中的模板引用 函数模板引用 组件上的 ref 简单理解Vue中的nextTick 示例 二、应用场景 三、nextTick源码浅析 实战 --- vue3实现编辑与查看功能 模板引用--ref 虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作&…

人工智能(pytorch)搭建模型10-pytorch搭建脉冲神经网络(SNN)实现及应用

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能(pytorch)搭建模型10-pytorch搭建脉冲神经网络&#xff08;SNN&#xff09;实现及应用&#xff0c;脉冲神经网络&#xff08;SNN&#xff09;是一种基于生物神经系统的神经网络模型&#xff0c;它通过模拟神…

计算机公式等级评定,《斗破苍穹》等级评定计算公式及评分细则

斗破V1.12防守版的等级评定的计算公式及评分细则 评分公式&#xff1a; 得分1杀怪数击杀BOSS得分英雄等级任务得分x2荣誉值/2狩猎值X3炼药得分/2-英雄死亡次数x50 得分2得分1满足下列条件分值&#xff1a; 死亡次数为0 200 击杀帝丹 150 击杀魂破天 250 击杀烈焰魂蛛 350…