Skip to content

Spring REST 客户端完全指南 🚀

引言:为什么需要 REST 客户端?

在现代微服务架构中,服务之间的通信主要通过 HTTP REST API 进行。想象一下,如果没有专门的 REST 客户端工具,我们每次调用外部 API 都需要:

  • 手动创建 HTTP 连接
  • 处理请求头和响应头
  • 序列化请求体和反序列化响应体
  • 处理各种异常情况
  • 管理连接池和超时设置

这将是一个繁琐且容易出错的过程!Spring Framework 为我们提供了四种强大的 REST 客户端解决方案,让 HTTP 调用变得简单而优雅。

IMPORTANT

Spring 提供了四种 REST 客户端选择:

  • RestClient - 现代化的同步客户端,流式 API
  • WebClient - 响应式异步客户端,支持非阻塞 I/O
  • RestTemplate - 传统的同步客户端,模板方法 API
  • HTTP Interface - 声明式接口,自动生成代理实现

RestClient:现代化的同步选择 ✨

核心理念与设计哲学

RestClient 是 Spring 6.1 引入的现代化同步 HTTP 客户端。它的设计哲学是:简洁、流畅、直观

相比传统的 RestTemplate,RestClient 采用了流式 API 设计,让代码读起来更像自然语言:

创建 RestClient

kotlin
// 使用默认配置创建
val defaultClient = RestClient.create()

// 自定义配置创建
val customClient = RestClient.builder()
    .baseUrl("https://api.example.com") 
    .defaultHeader("Authorization", "Bearer token") 
    .defaultHeader("Content-Type", "application/json")
    .requestFactory(HttpComponentsClientHttpRequestFactory())
    .build()
kotlin
@Configuration
class RestClientConfig {
    @Bean
    fun restClient(): RestClient {
        return RestClient.builder()
            .baseUrl("https://api.company.com")
            .defaultUriVariables(mapOf("version" to "v1")) 
            .defaultHeader("User-Agent", "MyApp/1.0")
            .defaultHeader("Accept", "application/json")
            .requestInterceptor { request, body, execution ->
                // 添加请求日志
                println("发送请求: ${request.method} ${request.uri}")
                execution.execute(request, body)
            }
            .build()
    }
}

基础用法示例

GET 请求:获取数据

kotlin
data class User(
    val id: Long,
    val name: String,
    val email: String
)

@Service
class UserService(private val restClient: RestClient) {
    fun getUserById(id: Long): User? {
        return restClient.get() 
            .uri("/users/{id}", id) 
            .accept(MediaType.APPLICATION_JSON)
            .retrieve()
            .body<User>() 
    }
    // 获取响应头信息
    fun getUserWithHeaders(id: Long): ResponseEntity<User> {
        return restClient.get()
            .uri("/users/{id}", id)
            .retrieve()
            .toEntity<User>() 
    }
}

POST 请求:创建数据

kotlin
data class CreateUserRequest(
    val name: String,
    val email: String
)

@Service
class UserService(private val restClient: RestClient) {
    fun createUser(request: CreateUserRequest): User {
        return restClient.post() 
            .uri("/users")
            .contentType(MediaType.APPLICATION_JSON)
            .body(request) 
            .retrieve()
            .body<User>()
    }
    // 处理创建成功但无返回体的情况
    fun createUserNoResponse(request: CreateUserRequest): ResponseEntity<Void> {
        return restClient.post()
            .uri("/users")
            .contentType(MediaType.APPLICATION_JSON)
            .body(request)
            .retrieve()
            .toBodilessEntity() 
    }
}

高级特性

错误处理

WARNING

默认情况下,RestClient 会对 4xx 和 5xx 状态码抛出异常。我们需要自定义错误处理逻辑。

kotlin
@Service
class UserService(private val restClient: RestClient) {
    fun getUserSafely(id: Long): User? {
        return try {
            restClient.get()
                .uri("/users/{id}", id)
                .retrieve()
                .onStatus(HttpStatusCode::is4xxClientError) { _, response ->
                    when (response.statusCode.value()) {
                        404 -> throw UserNotFoundException("用户不存在: $id")
                        401 -> throw UnauthorizedException("未授权访问")
                        else -> throw ClientException("客户端错误: ${response.statusCode}")
                    }
                }
                .onStatus(HttpStatusCode::is5xxServerError) { _, response ->
                    throw ServerException("服务器错误: ${response.statusCode}")
                }
                .body<User>()
        } catch (e: UserNotFoundException) {
            null // 用户不存在时返回 null
        }
    }
}

Exchange 模式:完全控制

当需要更精细的控制时,可以使用 exchange() 方法:

kotlin
fun getUserWithFullControl(id: Long): User? {
    return restClient.get()
        .uri("/users/{id}", id)
        .exchange { request, response ->
            when {
                response.statusCode.is2xxSuccessful -> {
                    // 成功响应,解析用户数据
                    convertResponseToUser(response)
                }
                response.statusCode.value() == 404 -> {
                    // 用户不存在
                    null
                }
                response.statusCode.is4xxClientError -> {
                    throw ClientException("请求错误: ${response.statusCode}")
                }
                else -> {
                    throw ServerException("服务器错误: ${response.statusCode}")
                }
            }
        }
}

private fun convertResponseToUser(response: ClientHttpResponse): User {
    // 自定义响应解析逻辑
    val objectMapper = ObjectMapper()
    return objectMapper.readValue(response.body, User::class.java)
}

实际业务场景示例

电商系统中的订单服务

完整的订单服务实现示例
kotlin
data class Order(
    val id: String,
    val userId: Long,
    val items: List<OrderItem>,
    val totalAmount: BigDecimal,
    val status: OrderStatus
)

data class OrderItem(
    val productId: Long,
    val quantity: Int,
    val price: BigDecimal
)

enum class OrderStatus {
    PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED
}

@Service
class OrderService(
    private val restClient: RestClient,
    private val logger: Logger = LoggerFactory.getLogger(OrderService::class.java)
) {
    // 创建订单
    fun createOrder(userId: Long, items: List<OrderItem>): Order {
        val request = mapOf(
            "userId" to userId,
            "items" to items
        )
        return restClient.post()
            .uri("/orders")
            .contentType(MediaType.APPLICATION_JSON)
            .body(request)
            .retrieve()
            .onStatus(HttpStatusCode::is4xxClientError) { _, response ->
                logger.error("订单创建失败: ${response.statusCode}")
                throw OrderCreationException("订单创建失败")
            }
            .body<Order>()
    }
    // 查询订单状态
    fun getOrderStatus(orderId: String): OrderStatus? {
        return try {
            val order = restClient.get()
                .uri("/orders/{orderId}/status", orderId)
                .retrieve()
                .body<Order>()
            order?.status
        } catch (e: Exception) {
            logger.warn("查询订单状态失败: $orderId", e)
            null
        }
    }
    // 批量查询用户订单
    fun getUserOrders(userId: Long, page: Int = 0, size: Int = 10): List<Order> {
        return restClient.get()
            .uri { builder ->
                builder.path("/users/{userId}/orders")
                    .queryParam("page", page)
                    .queryParam("size", size)
                    .build(userId)
            }
            .retrieve()
            .body<List<Order>>() ?: emptyList()
    }
}

WebClient:响应式的未来 🌊

为什么选择 WebClient?

WebClient 是 Spring 5.0 引入的响应式 HTTP 客户端,专为现代高并发应用设计。它解决了传统同步客户端的几个关键问题:

  • 阻塞 I/O 问题:传统客户端每个请求都会阻塞一个线程
  • 资源利用率低:大量线程等待 I/O 操作完成
  • 扩展性限制:线程数量限制了并发请求数量

TIP

WebClient 特别适合以下场景:

  • 高并发微服务调用
  • 需要组合多个异步操作
  • 流式数据处理
  • 需要背压控制的场景
kotlin
@Service
class ReactiveUserService(private val webClient: WebClient) {
    // 响应式获取用户信息
    fun getUserAsync(id: Long): Mono<User> {
        return webClient.get()
            .uri("/users/{id}", id)
            .retrieve()
            .bodyToMono<User>()
    }
    // 组合多个异步操作
    fun getUserWithOrders(userId: Long): Mono<UserWithOrders> {
        val userMono = getUserAsync(userId)
        val ordersMono = webClient.get()
            .uri("/users/{userId}/orders", userId)
            .retrieve()
            .bodyToFlux<Order>()
            .collectList()
        return Mono.zip(userMono, ordersMono) { user, orders ->
            UserWithOrders(user, orders)
        }
    }
}

RestTemplate:经典而稳定 🏛️

何时仍然选择 RestTemplate?

虽然 RestTemplate 在 Spring 5.0 后进入维护模式,但在某些场景下仍然是合适的选择:

NOTE

RestTemplate 适用场景:

  • 遗留系统维护
  • 简单的同步调用
  • 团队对响应式编程不熟悉
  • 需要与旧版本 Spring 兼容

RestTemplate 与 RestClient 对比迁移

kotlin
@Service
class UserServiceOld(private val restTemplate: RestTemplate) {

    fun getUser(id: Long): User? {
        return try {
            restTemplate.getForObject( 
                "/users/{id}",
                User::class.java,
                id
            )
        } catch (e: HttpClientErrorException) {
            if (e.statusCode == HttpStatus.NOT_FOUND) {
                null
            } else {
                throw e
            }
        }
    }
}
kotlin
@Service
class UserServiceNew(private val restClient: RestClient) {
    fun getUser(id: Long): User? {
        return restClient.get() 
            .uri("/users/{id}", id) 
            .retrieve() 
            .onStatus(HttpStatusCode::is4xxClientError) { _, response ->
                if (response.statusCode.value() == 404) { 
                    // 返回 null 而不是抛异常
                } else { 
                    throw ClientException("请求失败") 
                } 
            } 
            .body<User>() 
    }
}

HTTP Interface:声明式的优雅 🎭

设计理念:像定义接口一样定义 API

HTTP Interface 是 Spring 6.0 引入的声明式 HTTP 客户端,灵感来自于 Feign。它的核心思想是:将 HTTP 调用抽象为接口方法

kotlin
// 定义 API 接口
@HttpExchange("/api/v1")
interface UserApiClient {

    @GetExchange("/users/{id}")
    fun getUser(@PathVariable id: Long): User

    @PostExchange("/users")
    fun createUser(@RequestBody user: CreateUserRequest): User

    @PutExchange("/users/{id}")
    fun updateUser(
        @PathVariable id: Long,
        @RequestBody user: UpdateUserRequest
    ): User

    @DeleteExchange("/users/{id}")
    fun deleteUser(@PathVariable id: Long): ResponseEntity<Void>

    // 支持查询参数
    @GetExchange("/users")
    fun searchUsers(
        @RequestParam name: String?,
        @RequestParam email: String?,
        @RequestParam page: Int = 0,
        @RequestParam size: Int = 10
    ): List<User>
}

创建代理实现

kotlin
@Configuration
class HttpInterfaceConfig {
    @Bean
    fun userApiClient(): UserApiClient {
        val restClient = RestClient.builder()
            .baseUrl("https://api.example.com")
            .defaultHeader("Authorization", "Bearer ${token}")
            .build()

        val adapter = RestClientAdapter.create(restClient)
        val factory = HttpServiceProxyFactory.builderFor(adapter).build()

        return factory.createClient(UserApiClient::class.java) 
    }
}

在服务中使用

kotlin
@Service
class UserService(private val userApiClient: UserApiClient) {

    fun findUser(id: Long): User {
        return userApiClient.getUser(id) 
    }

    fun searchUsers(criteria: SearchCriteria): List<User> {
        return userApiClient.searchUsers( 
            name = criteria.name,
            email = criteria.email,
            page = criteria.page,
            size = criteria.size
        )
    }
}

自定义参数解析器

当需要处理复杂的请求参数时,可以实现自定义参数解析器:

kotlin
data class SearchCriteria(
    val name: String?,
    val email: String?,
    val ageRange: IntRange?,
    val tags: List<String>?
)

class SearchCriteriaArgumentResolver : HttpServiceArgumentResolver {
    override fun resolve(
        argument: Any?,
        parameter: MethodParameter,
        requestValues: HttpRequestValues.Builder
    ): Boolean {
        if (parameter.parameterType == SearchCriteria::class.java) {
            val criteria = argument as SearchCriteria

            criteria.name?.let {
                requestValues.addRequestParameter("name", it)
            }
            criteria.email?.let {
                requestValues.addRequestParameter("email", it)
            }
            criteria.ageRange?.let { range ->
                requestValues.addRequestParameter("minAge", range.first.toString())
                requestValues.addRequestParameter("maxAge", range.last.toString())
            }
            criteria.tags?.let { tags ->
                tags.forEach { tag ->
                    requestValues.addRequestParameter("tags", tag)
                }
            }
            return true
        }
        return false
    }
}

选择指南:何时使用哪种客户端? 🤔

性能对比

客户端并发性能内存占用学习曲线适用场景
RestClient⭐⭐⭐⭐⭐⭐⭐⭐现代同步应用
WebClient⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐高并发响应式应用
RestTemplate⭐⭐⭐⭐简单传统应用
HTTP Interface⭐⭐⭐⭐⭐⭐⭐⭐⭐微服务架构

最佳实践与注意事项 💡

1. 连接池配置

IMPORTANT

生产环境中必须配置合适的连接池参数,避免连接耗尽。

kotlin
@Configuration
class HttpClientConfig {
    @Bean
    fun restClient(): RestClient {
        val httpClient = HttpClients.custom()
            .setMaxConnTotal(200) 
            .setMaxConnPerRoute(50) 
            .setConnectionTimeToLive(30, TimeUnit.SECONDS)
            .build()

        val requestFactory = HttpComponentsClientHttpRequestFactory(httpClient)
        requestFactory.setConnectTimeout(5000) 
        requestFactory.setReadTimeout(10000) 

        return RestClient.builder()
            .requestFactory(requestFactory)
            .build()
    }
}

2. 错误处理策略

kotlin
@Component
class ApiErrorHandler {
    fun handleApiError(statusCode: HttpStatusCode, response: ClientHttpResponse) {
        when (statusCode.value()) {
            400 -> throw BadRequestException("请求参数错误")
            401 -> throw UnauthorizedException("认证失败,请重新登录")
            403 -> throw ForbiddenException("权限不足")
            404 -> throw NotFoundException("资源不存在")
            429 -> throw RateLimitException("请求过于频繁,请稍后重试")
            in 500..599 -> throw ServerException("服务器内部错误")
            else -> throw ApiException("未知错误: $statusCode")
        }
    }
}

3. 请求重试机制

kotlin
@Service
class ResilientApiService(private val restClient: RestClient) {
    @Retryable(
        value = [ServerException::class],
        maxAttempts = 3,
        backoff = Backoff(delay = 1000, multiplier = 2.0)
    )
    fun callApiWithRetry(url: String): String {
        return restClient.get()
            .uri(url)
            .retrieve()
            .onStatus(HttpStatusCode::is5xxServerError) { _, response ->
                throw ServerException("服务器错误: ${response.statusCode}")
            }
            .body<String>()
    }
}

总结 📝

Spring 的 REST 客户端生态系统为不同的应用场景提供了完整的解决方案:

  • RestClient 是现代同步应用的首选,提供流畅的 API 和强大的功能
  • WebClient 是高并发响应式应用的最佳选择,支持非阻塞 I/O
  • RestTemplate 虽然较老,但在简单场景下仍然实用
  • HTTP Interface 提供了声明式的优雅方案,特别适合微服务架构

TIP

对于新项目,推荐优先考虑 RestClient 或 HTTP Interface。如果需要高并发处理,选择 WebClient。只有在维护遗留系统时才考虑 RestTemplate。

选择合适的客户端,配合正确的配置和最佳实践,将让你的 HTTP 调用既高效又可靠! 🎉