Appearance
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 调用既高效又可靠! 🎉