🍮 后端框架
在之前的章节中我们已经介绍过,Forest分为前端和后端两部分,而后端是由okhttp3
和httpclient
这样的后端HTTP框架构成的
# 为何需要不同的后端框架
可能有些小伙伴会有疑问,既然某一种HTTP包(比如okhttp3
)可以作为后端的底层HTTP框架,已提供了日常所有的HTTP请求访问功能,为何还再支持另一种不同的HTTP框架作为后端呢?
这是因为没有一种框架都是完美的,都有各自的优缺点以及差异,比如okhttp3
接入相对简单、同步异步容易切换,但很难支持带请求体的GET请求这种非标准的畸形请求(但往往业务中需要这种类型请求);
而httpclient(5.0版本以下)
性能较好,支持各种标准和非标准请求,但接入较为麻烦,还需要依赖很多的jar包,且不支持HTTP/2
协议
Forest在某种程度上可以被理解为是一个屏蔽层,尽可能地屏蔽不同后端底层HTTP框架之间的差异,比如有统一的接口调用方式、统一的配置等等,但还是有些底层的特性差异是Forest无能为力的,比如让okhttp3
的GET请求携带Body这件事就难以做到
对于这样的问题,Forest给出的解决方案就是组合使用不同的后端框架,换句话说就是在需要的地方使用相应的后端框架
# 全局后端框架
Forest有一个全局唯一的后端,并以此为HTTP请求的默认后端,默认情况下为okhttp3
# 设置全局后端框架
# 目前版本有两种选择:okhttp3 和 httpclient
# 不填的默认请求为 okhttp3
forest:
backend: httpclient # 设置全局后端为 httpclient
2
3
4
5
6
# 设置全局后端框架
# 目前版本有两种选择:okhttp3 和 httpclient
# 不填的默认请求为 okhttp3
forest:
backend: okhttp3 # 设置全局后端为 okhttp3
2
3
4
5
6
# 设置全局后端框架
# 目前版本有两种选择:okhttp3 和 httpclient
# 不填的默认请求为 okhttp3
# 设置全局后端为 httpclient
forest.backend=httpclient
2
3
4
5
6
# 设置全局后端框架
# 目前版本有两种选择:okhttp3 和 httpclient
# 不填的默认请求为 okhttp3
# 设置全局后端为 okhttp3
forest.backend=okhttp3
2
3
4
5
6
<!-- backend 后端HTTP API: httpclient -->
<forest:configuration
... ...
backend="httpclient">
... ...
</forest:configuration>
2
3
4
5
6
<!-- backend 后端HTTP API: okhttp3 -->
<forest:configuration
... ...
backend="okhttp3">
... ...
</forest:configuration>
2
3
4
5
6
// 目前版本有两种选择:okhttp3 和 httpclient
// 不填的默认请求为 okhttp3
// 获取全局默认配置对象
ForestConfiguration configuration = Forest.config();
// 设置全局后端为 httpclient
configuration.setBackend(new HttpclientBackend());
2
3
4
5
6
7
// 目前版本有两种选择:okhttp3 和 httpclient
// 不填的默认请求为 okhttp3
// 获取Forest全局配置对象
ForestConfiguration configuration = Forest.config();
// 设置全局后端框架为 okhttp3
configuration.setBackend(new OkHttp3Backend());
2
3
4
5
6
7
// Make sure to add code blocks to your code group
如果要通过代码方式设置后端框架,建议将后端对象作为静态常量常驻于内存,而不是经常重复实例化同样的后端对象
public class MyBackend {
// httpclient 后端对象
public final static HttpclientBackend HTTPCLIENT = new HttpclientBackend();
// okhttp3 后端对象
public final static OkHttp3Backend OKHTTP3 = new OkHttp3Backend();
}
2
3
4
5
6
// 获取Forest全局配置对象
ForestConfiguration configuration = Forest.config();
// 设置全局后端框架为 httpclient
configuration.setBackend(MyBackend.HTTPCLIENT);
2
3
4
// 获取Forest全局配置对象
ForestConfiguration configuration = Forest.config();
// 设置全局后端框架为 okhttp3
configuration.setBackend(MyBackend.OKHTTP3);
2
3
4
# 接口/请求后端框架
全局后端框架可以配置和切换,甚至可以进行动态切换,但很多时候需要同时使用不同的后端框架,比如两个不同的接口分别使用不同的后端
自1.5.5
版本后,Forest支持了接口/请求级别的后端框架配置,方便HTTP请求灵活设置后端
# 后端快捷注解
Forest提供了 @HttpClient
注解和 OkHttp3
注解,分别用于绑定请求的后端为 httpclient
和 okhttp3
// 绑定请求的后端为 httpclient
@HttpClient
@Post("/data1")
String send1(@Body MyUser user);
// 绑定请求的后端为 okhttp3
@OkHttp3
@Post("/data2")
String send2(@Body MyUser user);
2
3
4
5
6
7
8
9
如果此类注解也绑定到接口上,那么该接口下的所有方法的请求默认为该接口注解指定的后端框架
// 设置该请求接口的后端框架默认为 httpclient
@HttpClient
public interface BackendClient2 {
// 未设置请求的后端,则默认为接口指定的后端框架,即 httpclient
@Post("/data1")
String send1(@Body MyUser user);
// 绑定某一方法请求的后端为 okhttp3
// 会覆盖掉接口上绑定的后端
@OkHttp3
@Post("/data2")
String send2(@Body MyUser user);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# @Backend 注解
还有一种更为灵活和通用的注解 @Backend
, 可以通过传入的字符串参数来确定具体要绑定的后端框架
// 设置该请求接口的后端框架默认为 httpclient
@Backend("httpclient")
public interface BackendClient2 {
// 未设置请求的后端,则默认为接口指定的后端框架,即 httpclient
@Post("/data1")
String send1(@Body MyUser user);
// 绑定请求的后端为 okhttp3
@Backend("okhttp3")
@Post("/data2")
String send2(@Body MyUser user);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
该注解的参数也支持字符串模板,即可以通过全局变量和参数来动态传入
@Backend("{0}")
@Post("/data")
String send(String backend, @Body MyUser user);
2
3
提示
对于如何在非 spring/springboot 项目中通过代码设置后端框架,请参见《请求对象 - 后端框架》
# 自定义后端 Client 对象
Forest 在默认情况下会自动生成后端 Client 对象实例,并进行缓存。但如果您想进行更细致的操作时也可以自行生成和配置后端 Client 对象。
# OkHttpClient
自定义 OkHttp3 框架的 OkHttpClient 对象,只需实现OkHttpClientProvider
接口
public class MyOkHttpClientProvider implements OkHttpClientProvider {
@Override
public OkHttpClient getClient(ForestRequest request, LifeCycleHandler lifeCycleHandler) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(700, TimeUnit.SECONDS)
.readTimeout(700, TimeUnit.SECONDS)
.writeTimeout(700, TimeUnit.SECONDS)
.callTimeout(700, TimeUnit.SECONDS)
.followSslRedirects(false)
.retryOnConnectionFailure(false)
.followRedirects(false)
.build();
return okHttpClient;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
绑定该自定义的 OkHttpClient 对象提供者
@Get("/")
@OkHttp3(client = MyOkHttpClientProvider.class)
ForestResponse<String> sendData();
2
3
# HttpClient
同理,自定义 Apache Httpclient 框架的 HttpClient 对象,也只需实现HttpClientProvider
接口
public class MyHttpClientProvider implements HttpClientProvider {
@Override
public HttpClient getClient(ForestRequest request, LifeCycleHandler lifeCycleHandler) {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(700 * 1000)
.setSocketTimeout(700 * 1000)
.setConnectionRequestTimeout(700 * 1000)
.setRedirectsEnabled(false)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultRequestConfig(config)
.build();
return httpClient;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
绑定该自定义的 HttpClient 对象提供者
@Get("/")
@HttpClient(client = MyHttpClientProvider.class)
ForestResponse<String> sendData();
2
3
# 后端 Client 缓存
为提高 Forest 请求的执行性能,默认情况下,每个请求所对应的后端客户端对象都会被缓存。
请求前,会先去缓存中寻找所需的后端 Client 对象实例,如若没有,则新创建一个并放入该请求所对应的缓存中。
当 Client 实例的数量达到缓存的最大空间大小时(默认为 128),就会按 W-TinyLFU 算法来驱逐使用频率最小的那个实例,以使得实例数量降低到缓存的容量大小。
并且如果 Client 实例在缓存中存在的时间过长,以至于超过了缓存的过期时间(默认为 6 个小时),也会被回收。
# 缓存的相关配置
自v1.5.36
版本起,Forest 的后端 Client 缓存可以进行配置了
forest:
backend-client-cache-max-size: 512 # 后端客户端缓存最大空间大小(单位为实例个数,默认为128)
backend-client-cache-expire-time: 3h # 后端客户端缓存超时时间(单位为时间长度,默认为6小时)
2
3
# 后端客户端缓存最大空间大小(单位为实例个数,默认为128)
forest.backend-client-cache-max-size=512
# 后端客户端缓存超时时间(单位为时间长度,默认为6小时)
forest.backend-client-cache-expire-time=3h
2
3
4
ForestConfiguration configuration = Forest.config();
// 后端客户端缓存最大空间大小(单位为实例个数,默认为128)
configuration.setBackendClientCacheMaxSize(512);
// 后端客户端缓存超时时间(单位为时间长度,默认为6小时)
configuration.setBackendClientCacheExpireTime(Duration.ofHours(3));
2
3
4
5
// Make sure to add code blocks to your code group
# 缓存开关
如果您不想让某个方法的请求的 Client 实例由 Forest 的缓存来管理,也可以使用@BackendClient
注解来打开/关闭其缓存。
接口的缓存开关设定如下:
// 关闭后端 Client 缓存
@Get("/")
@BackendClient(cache = false)
ForestRequest<String> sendData();
2
3
4