Releases: dromara/forest
v1.7.1
Forest v1.7.1 版本发布了!本次发布包含了很多重大更新内容,我们对字符串模板的语法进行了增强,支持了空安全语法和深度变量引用,并提供了更友好的报错信息;在Cookie方面也进行了增强,提供了Cookie自动保存和读取的机制,并添加了更完善的API接口;我们也对拦截器进行了优化改进,不再建议直接使用Interceptor
接口和它的onSuccess
方法,取而代之的是ForestInterceptor
和onResponse
方法,它们要比前者更安全,性能也更好;除此之外,此次更新对请求的性能进行了全面的优化,在默认使用编程式接口、不打印日志的情况下,可以达到和 hutool 的 HttpUtil 差不多的耗时。
空安全语法
在原来版本,如果在字符串模板中引用一个没定义过的变量会支持报错。现在用空安全语法可以让它不再报错,而是直接返回 null 值。
// 没定义过 testVar 变量,通过 ? 一样可以正常引用
Forest.get("/test/{testVar?}");
或者,引用了一个不存在或值为 null 的变量后,再用点.
访问它的属性,这在老版本中自然是直接报错的。现在,可以通过?.
符号自动判断是否为空。
// 会先判断 testVar 是否为空,然后 testVar.a 是否为空,其中一个为空就会直接返回 null
// 不会报错
Forest.get("/test/{testVar?.a?.name}");
深度引用
在原来版本中,字符串模板中的变量只能引用到第一层,如果该变量的值是引用其他的变量,字符串模板并不会进行解析。而现在,不管引用了多少层变量,都可以解析到底。
forest:
variables:
var1: "{user.name}"
var2: "{user.password}"
user:
name: foo
password: bar
在Java代码中直接引用
// 最终 URL 为: /test/foo/bar
Forest.get("/test/{var1}/{var2}");
如果不想进行深度引用,可以使用深度引用停止语法,即在变量后加上!
符号,那么就只会引用一层该变量的值,至于该变量的值是否会包含其他字符串模板的内容,就不会再进行解析了。
// 最终 URL 为: /test/{user.name}/{user.password}
// 变量 var1 和 var2 的值直接返回字符串,而不会进行任何解析
Forest.get("/test/{var1!}/{var2!}");
更安全的拦截器
本次版本更新后,不再建议使用Interceptor
(当然要用也可以用,不影响以前的老代码),同时引入了更安全的ForestInterceptor
接口
public class MyInterceptor implements ForestInterceptor {
@Override
public ResponseResult onResponse(ForestRequest request, ForestResponse response) {
if (response.isError()) {
// 返回错误标志
return error(response.getException());
}
// 通过 response.getResult() 或 response.get(数据类型.class) 来获取响应数据
// 返回继续执行标志
return proceed();
}
}
请求级别日志开关
不再需要 new 一个 LogConfiguration 对象,直接在 ForestRequest 的链式调用中即可设置日志开关
Forest.post("/test")
.logEnabled(true) // 请求日志总开关
.logRequest(true) // 请求内容日志开关
.logResponseStatus(true) // 响应状态日志开关
.logResponseHeaders(true) // 响应头日志开关
.logResponseContent(false) // 响应体内容日志开关
.execute();
新增特性
- feat: 字符串模板支持空安全语法
- feat: 更友好的字符串模板错误消息
- feat: 字符串模板
{
、${
等符号支持转义\\{
、\\${
- feat: 字符串模板支持深度变量引用
- feat: 字符串模板支持停止深度引用的语法
- feat: 支持嵌套字符串模板
- feat: 新增更安全的 Forest 拦截器接口 ForestInterceptor
- feat: 通过配置自定义异步线程池拒绝策略
- feat: 支持Bear 认证器
- feat: 支持
@Var
作为方法和类的变量绑定注解 - feat: 新增 ForestRequest 级别的日志开关接口
- feat: 增强 Cookie 相关 API 接口
- feat: 后台自动清理过期 Cookie
- feat: 支持 Cookie 自动化存取机制
修复错误
- fix: 和老版本forest冲突时,新版本Forest类缺乏get(url)、post(url)等方法签名,造成错误 (#IC7LIH)
- fix: body log在部分环境中文乱码
- fix: 修改接口中常量的命名,避免用户在其与只有大小写区别的方法之间产生混淆
重构内容
- refactor: 重构URL解析过程
- refactor: 重构变量作用域
- refactor: 重构 Forest 变量体系
- refactor: ForestCookie 不再依赖 OkHttp
- refactor: ForestCookie.parse() 接口
- refactor: 添加键值对类型请求体删除接口 ForestBody.removeNameValueBody
代码优化
- optimize: 优化请求性能
- optimize: 根据 Response 类型动态判断响应是否自动关闭
- optimize: 默认后端改为 httpclient
- optimize: 拦截器优化
v1.6.4
Forest v1.6.3 版本发布了!此次版本更新主要调整了 SSE 消息处理方面的接口
SSE 消息行模式
SSE 的消息通常为标准的多行一组的name:value
格式,每组消息用空白行隔开,具体如下:
id:1
event:json
data:{"name":"a"}
text:xxx
id:2
event:json
data:{"name":"b"}
text:yyy
对于这种标准的格式,可使用多行(MULTI_LINES),或自动(AUTO)模式
Forest.get("/sse")
.sse()
.setOnMessage(event -> {
event.id(); // 消息名为 id 的值,这里应得到 1, 2
event.event(); // json
event.data(); // {"name": "a"}, ...
event.value("text"); // 获取非标准名称的消息值,如: text,这里应得到 xxx, yyy
})
.listen(SSELinesMode.MULTI_LINES);
或者使用AUTO
,listen
方法不传参数的情况下,默认为AUTO
,AUTO
模式会自动识别需要采用的行模式
// AUTO 模式会自动识别需要采用的行模式
sse.listen(); // 默认行模式为 AUTO
SSE 的消息除了有标准格式,还有很多非标准的格式,比如每行都是一条JSON字符串,每一行都是单独的消息
{"name":"a"}
{"name":"b"}
{"name":"c"}
对于这种类型的消息,就要使用单行模式(SINGLE_LINE)
Forest.get("/sse")
.sse()
.setOnMessage(event -> {
String str = event.value(); // 获取字符串类型的消息值
MyUser user = event.value(MyUser.class); // 获取消息值并转换为自定义类型
})
.listen(SSELinesMode.SINGLE_LINE);
新增特性
- feat: 添加根据类型获取body对象的接口,可通过
request.body().get(Class)
获得body中对应类型的对象 - feat: 支持指定 SSE 消息行模式,包括单行、多行、以及自动模式
修复问题
- fix: 由Content-Type中
; charest=utf8
部分包含空格导致的body无故乱码的问题 - fix: 和老版本forest冲突时,新版本Forest类缺乏get(url)、post(url)等方法签名,造成错误
v1.6.3
Forest v1.6.3 版本发布了!此次版本更新主要调整了 SSE 相关的API接口,以及更新了maven依赖
新增特性
- add: EventSource.value(Class) 方法
- add: EventSource.value(TypeReference) 方法
不兼容改动
- refactor: SSEInterceptor.onSSEClose(ForestRequest, ForestResponse) 方法参数改为 SSEInterceptor.onSSEClose(EventSource)
- refactor: ForestSSE.setOnClose(BiConsumer<ForestRequest, ForestResponse>) 方法参数改为 ForestSSE.setOnClose(Consumer)
- refactor: EventSource.getValue() 方法重命名为 EventSource.value()
- refactor: EventSource.getName() 方法重命名为 EventSource.name()
- refactor: EventSource.getRawData() 方法重命名为 EventSource.rawData()
其他改动
- update: 更新依赖版本
v1.6.2
Forest v1.6.2 版本发布了!此次版本更新主要修复了 JsonPath 和 SSE 相关问题,并新增了 SSE 的监听关闭接口。
新增特性
- feat: 新增 ForestSSE.close() 监听关闭接口
- feat: 新增 ForestSSE.await() 用于阻塞等待异步监听完成
修复问题
- fix: EventSource.close() 无法正常关闭 SSE 监听
- fix: 通过JsonPath获取基本类型字段错误
v1.6.1
Forest v1.6.1 版本发布了!此次版本更新主要修复了 SSE 相关问题,并新增了 SSE 拦截器。
SSE 拦截器
ForestSSE
控制器和自定义SSE控制器都是非单例的独立实例对象,所以无法直接注入和使用 spring 上下文中的资源。
而 SSE 拦截器接口 SSEIntercpetor 继承自 Interceptor,可以注入到 spring 上下文中并使用其中的 bean,但和普通拦截器一样是单例,不能使用类属性共享数据。
@Component
public class MySSEInterceptor implements SSEInterceptor {
@Override
public void onSuccess(InputStream data, ForestRequest request, ForestResponse response) {
// 和普通拦截器 onSuccess 相同,data 是 SSE 的消息流,不建议在这里动它
}
@Override
public void afterExecute(ForestRequest request, ForestResponse response) {
// 和普通拦截器 afterExecute 相同
}
@Override
public void onSSEOpen(EventSource eventSource) {
// SSE 开始监听时调用
}
@Override
public void onSSEClose(ForestRequest request, ForestResponse response) {
// SSE 结束监听时调用
}
// 监听名称为 data 的消息
@SSEDataMessage
public void onData(ForestRequest request, @SSEName String name, @SSEValue String value) {
// 参数列表中的参数个数不做限制,可随意排列组合 ForestRequest、ForestResponse、EventSource 这几个类型参数
// name: @SSEName 注解修饰的参数可传递 SSE 消息的名称
// value: @SSEValue 注解修饰的参数可传递 SSE 消息的值
}
// 监听名称为 event 的消息
@SSEEventMessage
public void onEvent(EventSource eventSource, @SSEValue String value) {
// 参数列表中的参数个数不做限制,可随意排列组合 ForestRequest、ForestResponse、EventSource 这几个类型参数
}
// 通过 @SSEMessage 注解可以指定要监听何种名称的消息
@SSEMessage("id")
public void onData(EventSource eventSource) {
// 参数列表中的参数个数不做限制,可随意排列组合 ForestRequest、ForestResponse、EventSource 这几个类型参数
}
}
绑定方式和普通拦截器一样
@Get(url = "/sse", interceptor = MySSEInterceptor.class)
ForestSSE testSSE_withInterceptor();
新增特性
- feat: SSE 拦截器
修复问题
- fix: @SSEName 注解参数取值错误
- fix: SSE 请求长连接超时
- fix: SSE 请求返回的控制器为单例的问题
代码改动
- update: SSE 适配 spring-boot 和 solon
v1.6.0
Forest v1.6.0 版本发布了!此次版本更新功能较多,新增了包括 SSE、Jsonpath、链式条件函数等新特性。
支持 SSE
此次最大的更新,主要是支持了 SSE,并且支持声名式和编程式两种 SSE 接口
声明式 SSE 接口:
接口定义:
public interface SSEClient {
// Forest SSE 控制器类作为 SSE 接口返回类型
@Get("/sse")
ForestSSE testSSE();
// 自定义 SSE 控制器类作为 SSE 接口返回类型
@Get("/sse")
MySSEHandler testSSE2();
}
自定义 SSE 控制器类:
public class MySSEHandler extends ForestSSE {
@Override
protected void onOpen(EventSource eventSource) {
// SSE 开始监听时执行
}
@Override
protected void onClose(ForestRequest request, ForestResponse response) {
// SSE 结束监听时执行
}
@SSEDataMessage
public void onHello(@SSEValue String value) {
// 监听名称为 data 的消息事件
// @SSEValue 注解修饰的 value 参数为消息的值
}
@SSEEventMessage(valueRegex = "\\{.*name.*\\}")
public void onEvent(@SSEValue Contact contact) {
// 监听名称为 event 的消息事件
// 并且消息要满足匹配正则表达式 "\\{.*name.*\\}" 的要求
}
}
接口调用:
// ForestSSE 作为返回值的接口调用
sseClient.testSSE().listen();
// 自定义 SSE 控制器作为返回值的接口调用
sseClient.testSSE().listen();
编程式 SSE 接口:
Forest.get("http://localhost:{}/", server.getPort())
.sse() // 指定请求为 SSE 请求
.setOnOpen(eventSource -> {
// SSE 开始监听时执行
})
.setOnClose((req, res) -> {
// SSE 结束监听时执行
})
.addOnData((eventSource, name, value) -> {
// 监听到名称为 data 的消息事件时执行
})
.addOnEventMatchesPrefix("close", (eventSource, name, value) -> {
// 监听到名称为 event 的消息事件,并且消息的值满足"close"前缀匹配要求时执行
eventSource.close(); // 手动关闭 SSE 监听
})
.listen(); // 开始监听
支持链式条件函数
Forest.get("http://localhost:{}", server.getPort())
.addHeader("A", 0)
.cond(b > 100, q -> q.addHeader("B", 100)) // [单分支] 如果 b > 100,则添加 Header B:100
.cond(c > 200, q -> q.addHeader("C", 200)) // [单分支] 如果 c > 200,则添加 Header C:100
.ifThen(a > 0, q -> q.addHeader("A", a + 1)) // [多分支] 如果 a > 0,则添加 Header A:a+1
.elseIfThen(a == 0, q -> q.addHeader("A", 0)) // [多分支] 但如果 a = 0,则添加 Header A:0
.elseIfThen(a == -1, q -> q.addHeader("A", -1)) // [多分支] 但如果 a = -1,则添加 Header A:-1
.elseIfThen(a == -2, q -> q.addHeader("A", -2)) // [多分支] 但如果 a = -2,则添加 Header A:-2
.elseThen(q -> q.addHeader("A", 10)) // [多分支] 否则添加 Header A:10
.execute();
支持 JsonPath
声名式 JsonPath 注解
public interface TestJSONPathClient {
@Get("/test/user")
@JSONPathResult("$.data")
TestUser getSingleUser();
@Get("/test/user")
@JSONPathResult("$.data")
List<TestUser> getListOfUsers();
@Get("/test/user")
@JSONPathResult("$.data[*].age")
List<Integer> getListOfUserAges();
@Get("/test/user")
@JSONPathResult("$.data[?(@.age>{minAge})].age")
List<Integer> getListOfUserAges(@Var("minAge") int minAge);
}
编程式 JsonPath 接口
TestUser user = Forest.get("http://localhost:{}/test/user", server.getPort())
.executeAsResponse()
.getByPath("$.data", TestUser.class);
安全流处理接口
Forest.get("http://localhost:{}/download/test-img.jpg", server.getPort())
.executeAsStream((in, req, res) -> {
// in 为已经打开的 InpuStream 流对象
// 在这里不需要手动打开和关闭流
});
大JSON数据的流式处理
List<MyUser> list = Forest.get("http://localhost:{}/user.json", server.getPort())
.executeAsStream((in, req, res) -> {
// 在回调函数返回的数据会作为请求的最终返回值
return new Gson()
.fromJson(new JsonReader(new InputStreamReader(in)), MyUser.class)
});
新增特性
- feat: 支持SSE
- feat: 链式条件接口
- feat: 支持Jsonpath
- feat: 字符串模板可以用 {} 替代 {数字} 作为参数占位符
- feat: 类级别
@BindingVar
注解 - feat: 适配Android环境的Log
- feat: 新增 onResponse 生命周期回调函数
- feat: 支持 Optional 作为接口返回结果类型
- feat: 支持 CompletableFuture 作为接口返回结果类型
- feat: 自定义 LogHandler 支持 Spring Bean 方式注入
- feat: 美化字符串表达式报错信息
- feat: 新增安全响应流处理接口
修复问题
- fix: 百分号无法被 URLEncode 的问题
- fix: @address 注解的属性无法重写的问题
- fix: 过滤器函数无法调用
- fix: 可能出现的 connection pool shut down 问题
- fix: 在后端为okhttp3情况下,打印content-type为multiparty/form-data,body为其他类型时报错的问题
- fix: 在拦截器的onMethodInitialized方法中调用setReturnType函数无效(#I8PJ9R)
- fix: 修复未恢复parentScope导致的栈溢出(#I9TP9Z)
代码改动
- update: 缓存框架换成Hutool的LRU缓存
- update: forest-solon-plugin 升级 solon 为:3.0.1(兼容 2.5.9+)
- update: 更新依赖最新稳定版本
- refactor: 在发生 Response: [Network Error] 错误时,可以打印 status 信息
- opt: query支持线程安全
- reflector: 重构 ForestAuthenticator 接口
- refactor: 重构响应的流处理逻辑
- add: ResultGetter.openStream 方法
- refactor: 修改表达式异常报错信息格式
贡献者
- @wittplus (witt)
- @Kiroe (Kiro)
- @noear_admin (西东)
v1.5.36
Forest v1.5.36 版本发布了!此次改动主要支持了 Fastjson2,以及 solon 版本更新
新增特性
- feat: 支持 Fastjson2
修复问题
- fix: okhttp 后端
response.getContentLength()
取不到值 (#I90MUX) - fix: Jackson转换器中Lazy转换Map出错
代码改动
- Update: solon 升级为
v2.6.5
- refactor: 不在生成 multipart boundary 字符串时使用 okio 包的方法
v1.5.35
v1.5.35
版本发布了!此次版本更新主要实现了后端客户端缓存可配置空间大小以及过期时间
配置后端客户端缓存
配置缓存大小和过期时间
forest:
backend-client-cache-max-size: 512 # 后端客户端缓存最大空间大小(单位为实例个数,默认为128)
backend-client-cache-expire-time: 3h # 后端客户端缓存超时时间(单位为时间长度,默认为6小时)
新增特性:
- feat: 后端客户端实例缓存可配置大小以及过期时间
v1.5.34
v1.5.34
版本发布了!此次更新为解决请求不同的域名很多时,内存消耗越来越大的问题,使用 Caffeine 缓存框架作为后端客户端缓存的缓存
修复问题
- fix: 当请求不同的域名很多时,内存消耗越来越大 (#I8J5PN)
- fix: 高并发环境下,监控runningPoolSize值,出现负值情况&且有时候所有请求都结束了,但值没有
归零 (#I8JNBU ) - fix:完善单元测试之修复声明式接口,
@BaseRequest
或@BaseURL
,在有baseURL属性下,如果方法的完整URL不写端口,就会被baseURL属性的端口覆盖,并不是默认的80端口的bug - fix: 修复不管是是用
@Address
还是@BaseRequest
都无法将baseUrl和@Get()
中的url拼接在一起的bug (#I7CAYS) - fix:处理
@Addrees
注解中basePath已/
结尾,方法url不以/
开头,最终地址会出现//
的bug。 - fix: MultipartRequestBody 类冲突 (154)
代码改动
- refactor: 将gson改为单例
- refactor: 去掉
@DownloadFile
注解在下载文件时的进度日志 - refactor: 将 forest-spring-boot3 的相关测试用例移动到 forest-test 下
贡献者
- @chming
- @wittplus
- @MoonSets
- @noear_admin
- https://github.com/zhfish
- https://github.com/galaxy-sea
v1.5.33
Forest v1.5.33 发布了,此次版本更新主要支持了 Socks 协议的代理,以及组合注解的属性重写
新增特性:
- feat: 支持socks代理 (#I6MLMD)
- feat: 组合注解支持属性重写
修复问题:
- fix:
@Body
注解的数组参数无法正常解析为JSON数组 (#I7UPBR) - fix: Content-Type为application/xml的情况下,发送byte数组数据错误 (#I7F3F0)
- fix:
@JSONBody
Collection codes 报错 (#I7QLTS)
代码改动:
- add:
@SocksProxy
注解 - add:
@OverrideAttribute
注解 - opt: 优化URL更新方式
- update: forest-solon-plugin 升级 solon 为:2.4.0
鸣谢:
- forest-solon-plugin 升级 solon 为:2.4.0 由 @noear_admin 贡献
- 解决issure:#I7QLTS 由 @angle94 贡献
新特性如何使用:
-
Socks 代理的使用方式请看 声明式Socks代理文档 以及 编程式Socks代理文档
-
组合注解的属性重写请看 文档