Forest Spring Boot Starter 学习
Forest Spring Boot Starter 学习
1. 引言
Forest Spring Boot Stater
什么是 Forest?
Forest 是一个高层次、极简的声明式HTTP调用API框架。它的核心设计理念是让发送HTTP请求变得像调用本地Java方法一样简单直观。您不再需要手动编写繁琐的 HttpClient 或 RestTemplate 代码,也无需关心连接管理、参数序列化、响应解析等底层细节。Forest 通过优雅的注解和简洁的API,将这些复杂性封装起来,让开发者能够更专注于业务逻辑的实现。
想象一下,您只需要定义一个Java接口,并使用几个注解来描述您的HTTP请求(如URL、请求方法、参数、请求头等),Forest就能自动为您完成实际的HTTP通信。这不仅大大减少了模板代码,也使得API调用更加清晰、易于维护。
Forest 的主要特性和优势
Forest 之所以受到许多开发者的青睐,主要归功于其以下几个核心特性和优势:
- 声明式 API:这是 Forest 最显著的特点。通过
@Get,@Post,@Query,@Body等一系列直观的注解,您可以清晰地定义HTTP请求的各个方面。这种方式使得代码可读性极高,接口定义即文档。 - 易于集成 (Spring Boot Friendly):Forest 提供了
forest-spring-boot-starter,可以与 Spring Boot 项目无缝集成。只需添加依赖并进行少量配置(甚至零配置),即可快速上手。它会自动扫描和注册您定义的 Forest 客户端接口。 - 功能丰富:尽管 Forest 追求简洁,但其功能却非常全面。它支持所有标准的HTTP请求方法,能够灵活处理URL参数、请求体(包括表单、JSON、XML等)、请求头、Cookie等。此外,还内置了对数据自动转换(如JSON到POJO)、请求重试、异步请求、文件上传下载等常用功能的支持。
- 灵活扩展:Forest 具有良好的扩展性。您可以轻松自定义注解,实现特定的请求逻辑;可以自定义数据转换器,以支持不同的数据格式或序列化库;还可以通过拦截器(Interceptors)在请求的各个生命周期阶段插入自定义处理逻辑,如统一认证、日志记录、数据加解密等。
- 多种后端支持:Forest 底层支持多种成熟的 HTTP 客户端实现,如 OkHttp3 (默认) 和 Apache HttpClient。您可以根据项目需求或个人偏好选择合适的后端。
分享目标
通过本次分享,希望能够达到以下目标:
- 理解 Forest 框架的基本概念:了解什么是 Forest,它的核心思想以及主要解决什么问题。
- 掌握 Forest 的核心用法:学会如何在 Spring Boot 项目中引入和配置 Forest,如何定义客户端接口,以及如何使用常用注解发送 GET、POST 等请求,并处理响应数据。
- 了解 Forest 的一些进阶特性:初步认识 Forest 的异步请求、拦截器、重试机制等高级功能,为在实际项目中更灵活地运用 Forest 打下基础。
2. 环境准备与快速入门
在正式开始使用 Forest 之前,我们需要在 Spring Boot 项目中进行一些简单的环境配置。这个过程非常快捷,Forest 提供了良好的 Spring Boot Starter 支持。
依赖引入
首先,您需要在您的 Spring Boot 项目的 pom.xml (Maven) 或 build.gradle (Gradle) 文件中添加 forest-spring-boot-starter 依赖。
Maven 示例 (pom.xml):
<dependency>
<groupId>com.dtflys.forest</groupId>
<artifactId>forest-spring-boot-starter</artifactId>
<version>1.6.4</version> <!-- 建议检查并使用官方最新稳定版本 -->
</dependency>
可选依赖
Forest 默认使用 Jackson 进行 JSON 数据的序列化和反序列化。如果您的项目需要使用其他 JSON 库(如 Fastjson、Fastjson2)或需要处理 XML、Protobuf 等格式的数据,您可能需要引入额外的依赖。
-
JSON 处理: 如果您希望使用 Fastjson2,可以添加如下依赖:
<dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>LATEST_FASTJSON2_VERSION</version> <!-- 替换为最新的Fastjson2版本 --> </dependency>Forest 会自动检测并优先使用您引入的 JSON 库。
-
XML 处理 (JAXB): 如果需要与返回 XML 数据的 API 交互,并希望 Forest 自动将 XML 转换为 Java 对象,需要添加
forest-jaxb依赖:<dependency> <groupId>com.dtflys.forest</groupId> <artifactId>forest-jaxb</artifactId> <version>1.6.4</version> <!-- 与forest-spring-boot-starter版本保持一致或兼容 --> </dependency>
Spring Boot 项目配置
Forest 遵循“约定大于配置”的原则。在大多数情况下,引入 forest-spring-boot-starter 后,您无需进行任何额外配置即可开始使用。Forest 会提供一套合理的默认配置。
不过,如果您需要自定义一些全局的 HTTP 请求行为,可以在 Spring Boot 的配置文件 (application.yml 或 application.properties) 中进行设置。以下是一些常用的配置项:
YAML 示例 (application.yml):
forest:
backend: okhttp3 # 后端 HTTP 客户端,可选: okhttp3 (默认), httpclient
max-connections: 1000 # 全局最大连接数
max-route-connections: 500 # 每个路由的最大连接数
connect-timeout: 5000 # 连接超时时间 (毫秒)
read-timeout: 10000 # 数据读取超时时间 (毫秒)
retry-count: 0 # 全局请求重试次数,默认为0 (不重试)
log-enabled: true # 是否开启Forest的内置日志,默认为true
# 更多配置项,如SSL、代理等,请参考官方文档
Properties 示例 (application.properties):
forest.backend=okhttp3
forest.max-connections=1000
forest.max-route-connections=500
forest.connect-timeout=5000
forest.read-timeout=10000
forest.retry-count=0
forest.log-enabled=true
通常情况下,默认配置已经能满足大部分需求。只有在需要精细控制或优化性能时,才需要调整这些参数。
启用 Forest 接口扫描
为了让 Spring Boot 能够发现并管理您定义的 Forest 客户端接口,您需要在您的 Spring Boot 主启动类上添加 @ForestScan 注解,并指定包含这些接口的包路径。
package com.example.demo;
import com.dtflys.forest.springboot.annotation.ForestScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@ForestScan(basePackages = "com.example.demo.client") // 假设您的Forest接口都定义在 com.example.demo.client 包下
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
basePackages 属性可以指定一个或多个包路径。Forest 会扫描这些路径下的所有被正确注解的接口,并将它们注册为 Spring Bean,之后您就可以在其他组件中通过 @Autowired 等方式注入并使用了。
完成以上步骤后,您的 Spring Boot 项目就已经成功集成了 Forest 框架,可以开始定义和使用声明式的 HTTP 客户端接口了。
3. Forest 核心用法
环境配置完成后,我们就可以开始体验 Forest 强大而简洁的核心功能了。Forest 的核心在于通过定义 Java 接口和使用注解来描述 HTTP 请求。
定义 HTTP 客户端接口
在 Forest 中,所有的 HTTP 请求都是通过定义一个 Java 接口来实现的。这个接口充当了远程 HTTP API 的本地代理。您需要在接口的方法上使用 Forest 提供的注解来指定请求的详细信息。
例如,我们可以创建一个名为 MyApiClient.java 的接口:
package com.example.demo.client;
// import com.dtflys.forest.annotation.*; // 稍后会引入具体注解
public interface MyApiClient {
// 在这里定义具体的HTTP请求方法
}
确保这个接口所在的包路径 (例如 com.example.demo.client) 已经被 @ForestScan 注解扫描到。
发送 GET 请求
GET 请求是最常见的 HTTP 请求类型之一,通常用于从服务器获取数据。
-
@Get注解:用于标记一个方法为发送 GET 请求。其最重要的参数是请求的 URL。import com.dtflys.forest.annotation.Get; public interface MyApiClient { @Get(url = "http://localhost:8080/api/users/1") String getUserByIdStatic(); } -
URL 路径参数 (
@PathVariable):如果 URL 中的一部分是动态的(例如,用户ID),可以使用占位符{}并在方法参数上使用@PathVariable注解来传递。import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.PathVariable; public interface MyApiClient { @Get(url = "http://localhost:8080/api/users/{id}") String getUserById(@PathVariable("id") String userId); }Forest 会自动将
userId参数的值替换到 URL 中的{id}位置。 -
URL 查询参数 (
@Query):对于 URL 中的查询参数 (如?name=xxx&age=yyy),可以使用@Query注解。-
单个查询参数:
import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.Query; public interface MyApiClient { @Get(url = "http://localhost:8080/api/users/search") String findUserByName(@Query("username") String name); // 请求将是: http://localhost:8080/api/users/search?username=传入的name值 } -
多个查询参数 (POJO 或 Map):如果查询参数较多,可以将它们封装在一个 POJO 对象或 Map 中,并直接在方法参数上使用
@Query注解 (不指定名称)。// POJO 示例 public class UserQuery { private String username; private Integer age; // getters and setters } // 接口定义 import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.Query; public interface MyApiClient { @Get(url = "http://localhost:8080/api/users/filter") String findUsersByQueryObject(@Query UserQuery query); // Forest 会将 UserQuery 对象的属性作为查询参数,如: ?username=xxx&age=yyy @Get(url = "http://localhost:8080/api/users/filter_map") String findUsersByQueryMap(@Query java.util.Map<String, Object> queryMap); // Forest 会将 Map 的键值对作为查询参数 }
-
发送 POST 请求
POST 请求通常用于向服务器提交数据,例如创建新资源或提交表单。
-
@Post注解:用于标记一个方法为发送 POST 请求。 -
表单参数 (
@Body):当需要以application/x-www-form-urlencoded格式提交数据时,可以使用@Body注解。-
单个参数或多个参数:
import com.dtflys.forest.annotation.Post; import com.dtflys.forest.annotation.Body; public interface MyApiClient { @Post(url = "http://localhost:8080/api/users") String createUser(@Body("username") String name, @Body("password") String pass); } -
POJO 或 Map 作为表单参数:与
@Query类似,可以将表单参数封装在 POJO 或 Map 中。public class UserForm { private String username; private String password; // getters and setters } import com.dtflys.forest.annotation.Post; import com.dtflys.forest.annotation.Body; public interface MyApiClient { @Post(url = "http://localhost:8080/api/users/form") String createUserFromFormObject(@Body UserForm form); @Post(url = "http://localhost:8080/api/users/form_map") String createUserFromFormMap(@Body java.util.Map<String, Object> formMap); }
-
-
JSON 请求体 (
@JSONBody):当需要以application/json格式提交数据时,应使用@JSONBody注解。Forest 会自动将传递的 POJO 对象或 Map 序列化为 JSON 字符串作为请求体。import com.dtflys.forest.annotation.Post; import com.dtflys.forest.annotation.JSONBody; public class UserDto { private String username; private String email; // getters and setters } public interface MyApiClient { @Post(url = "http://localhost:8080/api/users/json") String createUserWithJson(@JSONBody UserDto userDto); @Post(url = "http://localhost:8080/api/users/json_map") String createUserWithJsonMap(@JSONBody java.util.Map<String, Object> userMap); }
其他常用请求方法
Forest同样支持其他标准的HTTP请求方法,使用方式与 @Get 和 @Post 类似:
@Put:用于更新资源。@Delete:用于删除资源。@Patch:用于部分更新资源。@Head:用于获取资源的元数据(响应头)。@Options:用于获取目标资源支持的通信选项。
示例:
import com.dtflys.forest.annotation.Put;
import com.dtflys.forest.annotation.Delete;
import com.dtflys.forest.annotation.PathVariable;
import com.dtflys.forest.annotation.JSONBody;
public interface MyApiClient {
@Put(url = "http://localhost:8080/api/users/{id}")
String updateUser(@PathVariable("id") String userId, @JSONBody UserDto userDto);
@Delete(url = "http://localhost:8080/api/users/{id}")
String deleteUser(@PathVariable("id") String userId);
}
处理响应数据
Forest 能够灵活地处理 HTTP 响应数据,并将其转换为不同类型的 Java 对象。
-
直接返回
String类型:如果响应体是文本内容(如纯文本、JSON字符串、XML字符串),可以直接将接口方法的返回类型定义为String。Forest 会自动读取响应体并作为字符串返回。 -
自动转换为 POJO 对象:如果响应体是 JSON 或 XML 格式,并且您已经配置了相应的转换器(Jackson默认支持JSON),Forest 可以自动将响应体反序列化为指定的 POJO 对象。
public class UserResponse { private Long id; private String username; private String email; // getters and setters } import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.PathVariable; public interface MyApiClient { @Get(url = "http://localhost:8080/api/users/{id}") UserResponse getUserObjectById(@PathVariable("id") String userId); } -
使用
ForestResponse<T>获取更详细的响应信息:如果您不仅需要响应体数据,还需要获取响应状态码、响应头等详细信息,可以将方法的返回类型定义为ForestResponse<T>,其中T是响应体期望转换的类型。import com.dtflys.forest.http.ForestResponse; import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.PathVariable; public interface MyApiClient { @Get(url = "http://localhost:8080/api/users/{id}") ForestResponse<UserResponse> getUserWithDetails(@PathVariable("id") String userId); }之后可以通过
ForestResponse对象的方法获取状态码 (response.getStatusCode())、响应头 (response.getHeaders())、响应体 (response.getResult()) 等。
设置请求头
有时需要在 HTTP 请求中添加自定义的请求头,例如传递认证令牌、指定内容类型等。
-
@Header注解 (静态请求头):可以直接在接口方法上使用@Header注解来定义固定的请求头。可以定义多个@Header注解。import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.Header; public interface MyApiClient { @Get(url = "http://localhost:8080/api/data") @Header(name = "X-Auth-Token", value = "your-static-token") @Header(name = "Accept-Language", value = "en-US") String getDataWithStaticHeaders(); } -
@Header注解配合方法参数 (动态请求头):如果请求头的值是动态的,可以将@Header注解用在方法参数上。import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.Header; public interface MyApiClient { @Get(url = "http://localhost:8080/api/secure/data") String getDataWithDynamicHeader(@Header("Authorization") String authToken); }调用该方法时传入的
authToken值将被用作Authorization请求头的值。也可以传递一个
Map<String, String>作为请求头集合:import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.Header; import java.util.Map; public interface MyApiClient { @Get(url = "http://localhost:8080/api/custom/data") String getDataWithHeaderMap(@Header Map<String, String> headers); }
以上是 Forest 的一些核心用法,通过这些注解和特性,您可以非常方便地构建和发送各种类型的 HTTP 请求。
4. Forest 进阶特性
除了上述核心用法外,Forest 还提供了许多进阶特性,可以帮助我们更灵活、更高效地处理复杂的 HTTP 请求场景。
异步请求
在需要进行非阻塞IO操作,或者同时发起多个HTTP请求以提高应用吞吐量时,异步请求非常有用。Forest 提供了便捷的方式来实现异步HTTP调用。
-
@Async注解:在接口方法上添加@Async注解,即可将该同步请求方法转换为异步执行。Forest 会在后台线程池中执行该HTTP请求。 -
返回类型:异步方法的返回类型通常是
java.util.concurrent.Future<T>或 Forest 提供的ForestResponse<T>(其内部也支持异步结果获取)。T代表期望的响应体转换类型。import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.Async; import com.dtflys.forest.http.ForestResponse; import java.util.concurrent.Future; public interface MyAsyncClient { @Async @Get(url = "http://localhost:8080/api/longTask") Future<String> performLongTaskAsync(); @Async @Get(url = "http://localhost:8080/api/anotherTask") ForestResponse<UserResponse> getAsyncUser(); } -
回调函数:Forest 支持为异步请求设置回调函数,以便在请求完成、成功或失败时执行特定逻辑。常用的回调接口有
OnSuccess,OnError,OnDone。import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.Async; import com.dtflys.forest.callback.OnSuccess; import com.dtflys.forest.callback.OnError; public interface MyAsyncClientWithCallback { @Async @Get(url = "http://localhost:8080/api/callbackTask") void performTaskWithCallbacks(OnSuccess<String> onSuccess, OnError onError); }调用时传入回调接口的实现:
// 在Service中调用 // myAsyncClientWithCallback.performTaskWithCallbacks( // (data, request, response) -> System.out.println("Success: " + data), // (exception, request, response) -> System.err.println("Error: " + exception.getMessage()) // );
拦截器 (Interceptors)
拦截器允许我们在请求的生命周期的不同阶段插入自定义逻辑,例如统一添加认证头、记录请求日志、对请求/响应数据进行加解密、监控请求耗时等。这是 Forest 非常强大的一个扩展点。
-
实现
Interceptor<T>接口:自定义拦截器需要实现com.dtflys.forest.interceptor.Interceptor<T>接口。T是响应结果的类型。 -
关键方法:
onInvoke(ForestRequest request, ForestMethod method, Object[] args): 在请求调用前执行,可以修改请求参数等。beforeExecute(ForestRequest request): 在请求实际发送前执行,可以修改请求头、请求体等。onSuccess(T data, ForestRequest request, ForestResponse response): 请求成功时回调。onError(ForestRuntimeException ex, ForestRequest request, ForestResponse response): 请求失败时回调。onRetry(ForestRequest request, ForestResponse response): 发生重试时回调。afterExecute(ForestRequest request, ForestResponse response): 请求执行完毕后回调(无论成功或失败)。
-
注册拦截器:
- 全局拦截器:通过配置类或在
application.yml中配置。# application.yml forest: interceptors: - com.example.interceptor.MyGlobalAuthInterceptor - com.example.interceptor.MyLoggingInterceptor - 接口/方法级别拦截器:在接口或方法上指定拦截器类。
import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.Interceptor; import com.example.interceptor.MySpecificInterceptor; @BaseRequest(baseURL = "http://localhost:8080/mock-api", interceptor = MyAuthInterceptor.class) // 应用于整个接口 public interface MySecureApiClient { @Get(url = "http://localhost:8080/api/secure/info",Interceptor=com.example.interceptor.AnotherInterceptor.class ) // 仅应用于此方法 String getSecureInfo(); }
- 全局拦截器:通过配置类或在
重试机制
网络请求中,临时性的网络波动或服务端抖动可能导致请求失败。Forest 提供了方便的重试机制来应对这种情况。
-
@Retry注解:在接口方法或接口上使用@Retry注解来配置重试策略。maxRetryCount: 最大重试次数。maxRetryInterval: 重试的最大间隔时间 (毫秒)。condition: 指定重试条件,可以是一个实现了RetryWhen接口的类。
import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.Retry; public interface MyRetryClient { @Get(url = "http://localhost:8080/api/unstableService") @Retry(maxRetryCount = "3", maxRetryInterval = "1000") // 最多重试3次,最大间隔1秒 String callUnstableService(); } -
全局重试配置:也可以在
application.yml中配置全局的重试次数。forest: retry-count: 2 # 全局默认重试2次方法级别的
@Retry配置会覆盖全局配置。
动态参数与动态 URL
Forest 允许非常灵活地处理动态参数和构建动态 URL。
-
方法参数直接作为请求参数:如前所述,通过
@Query,@Body,@Header,@PathVariable等注解,可以将方法参数灵活地映射到请求的各个部分。 -
在 URL 中使用占位符
{}:除了@PathVariable,还可以在 URL 字符串中直接使用{}占位符,其值会从方法参数中按名称或顺序匹配获取,或者从全局配置、变量池中获取。import com.dtflys.forest.annotation.Get; import com.dtflys.forest.annotation.Var; public interface MyDynamicUrlClient { // 参数名与占位符名称一致 @Get(url = "http://{domain}/api/data?version={ver}") String getDataFromDomain(@Var("domain") String domain, @Var("ver") String version); // 也可以在配置文件中定义全局变量 // forest.variables.defaultApiBase=http://my-api.com @Get(url = "{defaultApiBase}/users") String getAllUsers(); }
文件上传与下载 (简要介绍)
Forest 也支持文件上传和下载操作。
-
文件上传:通常通过
@Post或@Multipart注解,将java.io.File,byte[],InputStream或 Spring 的MultipartFile对象作为@Body或@DataFile参数传递。 -
文件下载:可以将接口方法的返回类型定义为
java.io.File,byte[],InputStream,或者ForestResponse<File>等。Forest 会将下载的文件内容写入到指定路径或作为流返回。
使用@DownloadFile注解可以更方便地控制下载行为,如指定保存路径、文件名等。
错误处理与自定义异常
Forest 有一套自己的异常体系,继承自 ForestRuntimeException。常见的有 ForestNetworkException (网络错误), ForestTimeoutException (超时错误), ForestConvertException (数据转换错误) 等。
您可以通过标准的 try-catch 块来捕获这些异常并进行处理。此外,如前所述,OnError 回调和拦截器的 onError 方法也提供了更细致的错误处理时机。
这些进阶特性使得 Forest 能够应对更多样化和复杂的 HTTP 通信需求,同时保持了其声明式的简洁风格。
5. Forest 编程式调用
除了前面介绍的声明式接口调用方式外,Forest 还提供了强大的编程式调用 API,让您可以更灵活地构建和发送 HTTP 请求。编程式调用支持链式风格,代码简洁易读,适合动态构建请求的场景。
5.1 编程式调用基础
Forest 的编程式调用主要通过 Forest 类提供的静态方法来实现,最基本的用法如下:
// GET 请求示例
String result = Forest.get("http://example.com/api/data")
.execute(String.class);
// POST 请求示例
String postResult = Forest.post("http://example.com/api/create")
.contentTypeJson()
.addBody("{\"name\":\"张三\",\"age\":30}")
.execute(String.class);
编程式调用的主要优势:
- 无需定义接口:直接在代码中构建和发送请求,无需预先定义接口
- 动态构建请求:可以根据运行时条件动态构建请求参数、URL等
- 链式调用风格:代码简洁易读,API 设计符合直觉
- 与业务代码紧密集成:可以直接在业务逻辑中构建和发送请求
5.2 常用请求方法
Forest 支持所有标准 HTTP 方法的编程式调用:
// GET 请求
Forest.get("http://example.com/api/data").execute();
// POST 请求
Forest.post("http://example.com/api/create").execute();
// PUT 请求
Forest.put("http://example.com/api/update").execute();
// DELETE 请求
Forest.delete("http://example.com/api/remove").execute();
// HEAD 请求
Forest.head("http://example.com/api/check").execute();
// OPTIONS 请求
Forest.options("http://example.com/api/options").execute();
// PATCH 请求
Forest.patch("http://example.com/api/partial-update").execute();
5.3 请求参数和请求体
5.3.1 添加查询参数
// 方式1:直接在URL中添加参数
String result1 = Forest.get("http://example.com/api/search?name=张三&age=30")
.execute(String.class);
// 方式2:使用addQuery方法添加参数
String result2 = Forest.get("http://example.com/api/search")
.addQuery("name", "李四")
.addQuery("age", 25)
.execute(String.class);
// 方式3:使用Map添加多个参数
Map<String, Object> params = new HashMap<>();
params.put("name", "王五");
params.put("age", 35);
String result3 = Forest.get("http://example.com/api/search")
.addQuery(params)
.execute(String.class);
5.3.2 添加请求体
// 发送JSON请求体(对象自动转换为JSON)
UserDto user = new UserDto(null, "赵六", "zhao6@example.com", "password123");
String jsonResponse = Forest.post("http://example.com/api/users")
.contentTypeJson()
.addBody(user)
.execute(String.class);
// 发送表单数据
String formResponse = Forest.post("http://example.com/api/users/form")
.contentTypeForm()
.addBody("username", "孙七")
.addBody("email", "sun7@example.com")
.execute(String.class);
// 发送原始字符串
String jsonString = "{\"username\":\"周八\",\"email\":\"zhou8@example.com\"}";
String rawResponse = Forest.post("http://example.com/api/users")
.contentTypeJson()
.addBody(jsonString)
.execute(String.class);
5.4 请求头和其他配置
// 添加请求头
Forest.get("http://example.com/api/data")
.addHeader("Authorization", "Bearer token123")
.addHeader("X-Custom-Header", "CustomValue")
.execute();
// 设置超时
Forest.get("http://example.com/api/data")
.connectTimeout(5000) // 连接超时时间(毫秒)
.readTimeout(5000) // 读取超时时间(毫秒)
.execute();
// 设置Content-Type
Forest.post("http://example.com/api/data")
.contentTypeJson() // 等同于 .contentType("application/json")
.addBody(jsonObject)
.execute();
// 其他常用Content-Type快捷方法
Forest.post("http://example.com/api/upload")
.contentTypeForm() // application/x-www-form-urlencoded
.addBody("key", "value")
.execute();
Forest.post("http://example.com/api/upload")
.contentTypeMultipart() // multipart/form-data
.addBody("file", new File("/path/to/file.jpg"))
.execute();
5.5 异步请求
Forest 支持异步请求,可以避免阻塞当前线程:
// 发送异步请求,立即返回Future对象
Future<String> future = Forest.get("http://example.com/api/async-data")
.async() // 标记为异步请求
.execute(String.class, false); // false表示返回结果而非ForestResponse
// 主线程可以继续执行其他任务
System.out.println("主线程继续执行...");
// 在需要结果时获取(如果请求未完成,此处会阻塞)
String result = future.get();
System.out.println("异步请求结果: " + result);
5.6 回调函数
Forest 提供了回调函数机制,可以处理请求成功和失败的情况:
// 使用匿名类
Forest.get("http://example.com/api/users/1")
.onSuccess(new OnSuccess<UserDto>() {
@Override
public void onSuccess(UserDto data, ForestRequest request, ForestResponse response) {
System.out.println("请求成功: " + data);
}
})
.onError(new OnError() {
@Override
public void onError(ForestRequest request, ForestResponse response, Exception ex) {
System.err.println("请求失败: " + ex.getMessage());
}
})
.execute(UserDto.class);
// 使用Lambda表达式(Java 8+)
Forest.get("http://example.com/api/users/1")
.onSuccess((data, req, resp) -> System.out.println("请求成功: " + data))
.onError((req, resp, ex) -> System.err.println("请求失败: " + ex.getMessage()))
.execute(UserDto.class);
5.7 编程式调用与声明式接口的对比
| 特性 | 编程式调用 | 声明式接口 |
|---|---|---|
| 定义方式 | 直接在代码中构建 | 预先定义接口和注解 |
| 灵活性 | 高,可动态构建请求 | 中,主要通过变量和表达式提供灵活性 |
| 代码组织 | 分散在业务代码中 | 集中在接口定义中 |
| 可维护性 | 对于简单请求较好,复杂请求可能导致代码冗长 | 接口定义清晰,易于维护 |
| 适用场景 | 临时请求、动态URL、参数不固定的场景 | 固定API调用、团队协作、大型项目 |
5.8 在Spring Boot中使用编程式调用
在Spring Boot项目中,您可以直接在任何组件中使用Forest的编程式调用:
@Service
public class UserService {
public UserDto getUserById(Long id) {
return Forest.get("http://api.example.com/users/" + id)
.execute(UserDto.class);
}
public List<UserDto> searchUsers(String keyword, int page, int size) {
return Forest.get("http://api.example.com/users/search")
.addQuery("keyword", keyword)
.addQuery("page", page)
.addQuery("size", size)
.execute(new TypeReference<List<UserDto>>() {});
}
}
5.9 编程式调用最佳实践
- 适度使用:对于频繁调用的固定API,优先使用声明式接口;对于临时性、动态性强的请求,使用编程式调用
- 封装共通逻辑:将常用的请求逻辑封装成工具方法,避免代码重复
- 统一错误处理:建立统一的错误处理机制,避免在每个请求中重复编写错误处理代码
- 合理使用异步:对于非阻塞场景,优先使用异步请求提高性能
- 注意资源释放:特别是在使用文件上传下载时,确保正确关闭资源
通过合理结合声明式接口和编程式调用,您可以充分发挥Forest框架的灵活性和强大功能,满足各种复杂的HTTP请求场景。

浙公网安备 33010602011771号