Forest Spring Boot Starter 学习

Forest Spring Boot Starter 学习

1. 引言

Forest Spring Boot Stater

什么是 Forest?

Forest 是一个高层次、极简的声明式HTTP调用API框架。它的核心设计理念是让发送HTTP请求变得像调用本地Java方法一样简单直观。您不再需要手动编写繁琐的 HttpClientRestTemplate 代码,也无需关心连接管理、参数序列化、响应解析等底层细节。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.ymlapplication.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);

编程式调用的主要优势:

  1. 无需定义接口:直接在代码中构建和发送请求,无需预先定义接口
  2. 动态构建请求:可以根据运行时条件动态构建请求参数、URL等
  3. 链式调用风格:代码简洁易读,API 设计符合直觉
  4. 与业务代码紧密集成:可以直接在业务逻辑中构建和发送请求

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 编程式调用最佳实践

  1. 适度使用:对于频繁调用的固定API,优先使用声明式接口;对于临时性、动态性强的请求,使用编程式调用
  2. 封装共通逻辑:将常用的请求逻辑封装成工具方法,避免代码重复
  3. 统一错误处理:建立统一的错误处理机制,避免在每个请求中重复编写错误处理代码
  4. 合理使用异步:对于非阻塞场景,优先使用异步请求提高性能
  5. 注意资源释放:特别是在使用文件上传下载时,确保正确关闭资源

通过合理结合声明式接口和编程式调用,您可以充分发挥Forest框架的灵活性和强大功能,满足各种复杂的HTTP请求场景。

posted @ 2025-05-21 16:35  知南而北游  阅读(689)  评论(0)    收藏  举报