Appearance
Spring Boot REST 服务调用完全指南 🚀
在现代微服务架构中,服务间的 HTTP 通信是家常便饭。Spring Boot 为我们提供了三种强大的 REST 客户端工具:WebClient、RestClient 和 RestTemplate。本文将深入探讨这些工具的使用场景、核心原理以及最佳实践。
为什么需要 REST 客户端? 🤔
NOTE
在微服务架构中,服务之间需要频繁进行 HTTP 通信。如果没有专门的客户端工具,我们就需要手动处理 HTTP 连接、序列化/反序列化、错误处理等繁琐工作。
想象一下,如果没有这些工具,我们调用一个 REST API 需要:
kotlin
// 繁琐的手动处理
val url = URL("https://api.example.com/users/123")
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.setRequestProperty("Content-Type", "application/json")
val responseCode = connection.responseCode
if (responseCode == 200) {
val inputStream = connection.inputStream
val response = inputStream.bufferedReader().use { it.readText() }
// 手动解析 JSON...
} else {
// 错误处理...
}
kotlin
// 简洁优雅的调用
@Service
class UserService(private val restClient: RestClient) {
fun getUser(id: String): User? {
return restClient.get()
.uri("/users/{id}", id)
.retrieve()
.body(User::class.java)
}
}
三大客户端工具对比 📊
特性 | WebClient | RestClient | RestTemplate |
---|---|---|---|
编程模型 | 响应式 (Reactive) | 阻塞式 (Blocking) | 阻塞式 (Blocking) |
适用场景 | 高并发、非阻塞应用 | 传统同步应用 | 遗留项目维护 |
API 风格 | 函数式链式调用 | 函数式链式调用 | 传统方法调用 |
Spring 版本 | Spring 5.0+ | Spring 6.1+ | Spring 3.0+ |
推荐程度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
TIP
选择建议:
- 🎯 WebClient:构建响应式应用,需要高并发处理
- 🎯 RestClient:传统 Spring MVC 应用,追求现代 API 体验
- 🎯 RestTemplate:维护遗留代码,不想大规模重构
WebClient:响应式编程的利器 ⚡
核心设计哲学
WebClient 基于 Project Reactor,采用响应式编程模型。它的设计哲学是:非阻塞 + 事件驱动,能够用少量线程处理大量并发请求。
基础使用示例
kotlin
@Service
class ProductService(
webClientBuilder: WebClient.Builder
) {
private val webClient = webClientBuilder
.baseUrl("https://api.store.com")
.build()
// 获取单个产品 - 返回 Mono
fun getProduct(id: String): Mono<Product> {
return webClient.get()
.uri("/products/{id}", id)
.retrieve()
.bodyToMono(Product::class.java)
}
// 获取产品列表 - 返回 Flux
fun getProducts(): Flux<Product> {
return webClient.get()
.uri("/products")
.retrieve()
.bodyToFlux(Product::class.java)
}
// POST 请求创建产品
fun createProduct(product: Product): Mono<Product> {
return webClient.post()
.uri("/products")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(product)
.retrieve()
.bodyToMono(Product::class.java)
}
}
WebClient 运行时配置
Spring Boot 会根据类路径自动选择最合适的 HTTP 客户端:
HTTP 客户端优先级
- Reactor Netty - 默认选择,性能最佳
- Jetty RS Client - 适合 Jetty 环境
- Apache HttpClient - 功能丰富
- JDK HttpClient - Java 11+ 内置
yaml
# 全局 HTTP 连接器配置
spring:
http:
reactiveclient:
connector: jetty # 指定使用 Jetty 客户端
connect-timeout: 2s
read-timeout: 1s
redirects: dont-follow
WebClient 自定义配置
kotlin
@Configuration
class WebClientConfig {
// 全局自定义器 - 影响所有 WebClient 实例
@Bean
fun webClientCustomizer(): WebClientCustomizer {
return WebClientCustomizer { builder ->
builder
.defaultHeader("User-Agent", "MyApp/1.0")
.defaultHeader("Accept", "application/json")
.codecs { configurer ->
configurer.defaultCodecs().maxInMemorySize(1024 * 1024) // 1MB
}
}
}
// 特定用途的 WebClient
@Bean
@Qualifier("paymentClient")
fun paymentWebClient(
builder: WebClient.Builder,
ssl: WebClientSsl
): WebClient {
return builder
.baseUrl("https://payment.api.com")
.apply(ssl.fromBundle("payment-ssl"))
.build()
}
}
WebClient SSL 支持
kotlin
@Service
class SecureApiService(
webClientBuilder: WebClient.Builder,
ssl: WebClientSsl
) {
private val secureClient = webClientBuilder
.baseUrl("https://secure-api.com")
.apply(ssl.fromBundle("my-ssl-bundle"))
.build()
fun callSecureEndpoint(): Mono<String> {
return secureClient.get()
.uri("/secure-data")
.retrieve()
.bodyToMono(String::class.java)
}
}
WARNING
SSL 配置需要在 application.yml
中预先定义 SSL Bundle:
yaml
spring:
ssl:
bundle:
jks:
my-ssl-bundle:
keystore:
location: classpath:keystore.jks
password: secret
RestClient:现代阻塞式客户端 🔄
设计理念
RestClient 是 Spring 6.1 引入的新客户端,结合了 RestTemplate 的阻塞特性和 WebClient 的现代 API 设计。它的核心理念是:简单直观 + 功能强大。
kotlin
@Service
class OrderService(
restClientBuilder: RestClient.Builder
) {
private val restClient = restClientBuilder
.baseUrl("https://api.orders.com")
.build()
// 同步调用,直接返回结果
fun getOrder(orderId: String): Order? {
return restClient.get()
.uri("/orders/{id}", orderId)
.retrieve()
.body(Order::class.java)
}
// 带错误处理的调用
fun updateOrder(order: Order): Order {
return restClient.put()
.uri("/orders/{id}", order.id)
.contentType(MediaType.APPLICATION_JSON)
.body(order)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError) { _, response ->
throw OrderValidationException("订单验证失败: ${response.statusText}")
}
.body(Order::class.java)!!
}
}
RestClient 高级特性
kotlin
@Service
class RobustApiService(restClientBuilder: RestClient.Builder) {
private val restClient = restClientBuilder
.baseUrl("https://api.example.com")
.build()
fun callApiWithErrorHandling(id: String): ApiResponse? {
return try {
restClient.get()
.uri("/data/{id}", id)
.retrieve()
.onStatus({ status -> status.is4xxClientError }) { _, response ->
when (response.statusCode.value()) {
404 -> throw ResourceNotFoundException("资源不存在: $id")
400 -> throw BadRequestException("请求参数错误")
else -> throw ApiException("客户端错误: ${response.statusText}")
}
}
.onStatus({ status -> status.is5xxServerError }) { _, response ->
throw ServerException("服务器错误: ${response.statusText}")
}
.body(ApiResponse::class.java)
} catch (ex: Exception) {
logger.error("API 调用失败", ex)
null
}
}
}
kotlin
@Configuration
class RestClientConfig {
@Bean
fun restClientCustomizer(): RestClientCustomizer {
return RestClientCustomizer { builder ->
builder
.requestInterceptor { request, body, execution ->
// 添加认证头
request.headers.add("Authorization", "Bearer ${getToken()}")
// 添加请求 ID 用于链路追踪
request.headers.add("X-Request-ID", UUID.randomUUID().toString())
val startTime = System.currentTimeMillis()
val response = execution.execute(request, body)
val duration = System.currentTimeMillis() - startTime
logger.info("API 调用: ${request.method} ${request.uri} - ${response.statusCode} (${duration}ms)")
response
}
}
}
private fun getToken(): String {
// 从认证服务获取 token
return "your-jwt-token"
}
}
RestClient SSL 配置
kotlin
@Service
class BankingService(
restClientBuilder: RestClient.Builder,
ssl: RestClientSsl,
sslBundles: SslBundles
) {
// 方式1:使用 RestClientSsl
private val bankingClient = restClientBuilder
.baseUrl("https://banking-api.com")
.apply(ssl.fromBundle("banking-ssl"))
.build()
// 方式2:使用 ClientHttpRequestFactorySettings (更多自定义选项)
private val advancedClient: RestClient
init {
val settings = ClientHttpRequestFactorySettings
.ofSslBundle(sslBundles.getBundle("banking-ssl"))
.withReadTimeout(Duration.ofMinutes(2))
.withConnectTimeout(Duration.ofSeconds(10))
val requestFactory = ClientHttpRequestFactoryBuilder
.detect()
.build(settings)
advancedClient = restClientBuilder
.baseUrl("https://advanced-banking-api.com")
.requestFactory(requestFactory)
.build()
}
fun transferMoney(transfer: MoneyTransfer): TransferResult {
return bankingClient.post()
.uri("/transfers")
.body(transfer)
.retrieve()
.body(TransferResult::class.java)!!
}
}
RestTemplate:经典而稳定 🏛️
为什么还需要了解 RestTemplate?
虽然 RestTemplate 在新项目中不再推荐,但在以下场景中仍然有其价值:
IMPORTANT
RestTemplate 适用场景:
- 维护遗留系统,避免大规模重构
- 团队对传统 API 更熟悉
- 需要与旧版本 Spring 保持兼容
RestTemplate 基础使用
kotlin
@Service
class LegacyApiService(
restTemplateBuilder: RestTemplateBuilder
) {
private val restTemplate = restTemplateBuilder
.rootUri("https://legacy-api.com")
.build()
// GET 请求
fun getUser(userId: String): User? {
return restTemplate.getForObject(
"/users/{id}",
User::class.java,
userId
)
}
// POST 请求
fun createUser(user: User): User? {
return restTemplate.postForObject(
"/users",
user,
User::class.java
)
}
// 更复杂的请求控制
fun updateUser(user: User): ResponseEntity<User> {
val headers = HttpHeaders().apply {
contentType = MediaType.APPLICATION_JSON
set("X-API-Version", "v1")
}
val entity = HttpEntity(user, headers)
return restTemplate.exchange(
"/users/{id}",
HttpMethod.PUT,
entity,
User::class.java,
user.id
)
}
}
RestTemplate 全局自定义
kotlin
@Configuration
class RestTemplateConfig {
// 全局自定义器
@Bean
fun restTemplateCustomizer(): RestTemplateCustomizer {
return RestTemplateCustomizer { restTemplate ->
// 设置超时
val requestFactory = restTemplate.requestFactory as SimpleClientHttpRequestFactory
requestFactory.setConnectTimeout(5000)
requestFactory.setReadTimeout(10000)
// 添加拦截器
restTemplate.interceptors.add { request, body, execution ->
request.headers.add("User-Agent", "MyLegacyApp/1.0")
execution.execute(request, body)
}
}
}
// 自定义 RestTemplateBuilder
@Bean
fun restTemplateBuilder(
configurer: RestTemplateBuilderConfigurer
): RestTemplateBuilder {
return configurer.configure(RestTemplateBuilder())
.connectTimeout(Duration.ofSeconds(5))
.readTimeout(Duration.ofSeconds(2))
.basicAuthentication("user", "password")
}
}
HTTP 客户端检测与配置 🔧
自动检测机制
Spring Boot 会根据类路径自动选择最合适的 HTTP 客户端:
全局 HTTP 客户端配置
yaml
# application.yml
spring:
http:
client:
factory: jetty # 强制使用 Jetty 客户端
connect-timeout: 2s
read-timeout: 1s
redirects: dont-follow
高级自定义配置
kotlin
@Configuration
class HttpClientConfig {
// 自定义 HTTP 客户端构建器
@Bean
fun clientHttpRequestFactoryBuilder(
proxySelector: ProxySelector
): ClientHttpRequestFactoryBuilder<*> {
return ClientHttpRequestFactoryBuilder.jdk()
.withHttpClientCustomizer { builder ->
builder
.proxy(proxySelector)
.connectTimeout(Duration.ofSeconds(10))
.executor(Executors.newCachedThreadPool())
}
}
// 代理选择器配置
@Bean
fun proxySelector(): ProxySelector {
return object : ProxySelector() {
override fun select(uri: URI?): List<Proxy> {
return when {
uri?.host?.endsWith(".internal") == true ->
listOf(Proxy.NO_PROXY) // 内部服务不走代理
else ->
listOf(Proxy(Proxy.Type.HTTP, InetSocketAddress("proxy.company.com", 8080)))
}
}
override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) {
logger.warn("代理连接失败: $uri", ioe)
}
}
}
}
实战案例:构建统一的 API 客户端 🛠️
让我们通过一个完整的实战案例,展示如何在真实项目中使用这些客户端:
完整的微服务客户端实现
kotlin
// 数据模型
data class User(
val id: String,
val name: String,
val email: String,
val createdAt: LocalDateTime
)
data class ApiResponse<T>(
val success: Boolean,
val data: T?,
val message: String?,
val timestamp: LocalDateTime = LocalDateTime.now()
)
// 异常定义
class ApiException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
class ResourceNotFoundException(message: String) : ApiException(message)
// 统一的 API 客户端
@Service
class UnifiedApiClient(
private val restClient: RestClient,
private val webClient: WebClient
) {
private val logger = LoggerFactory.getLogger(javaClass)
// 同步调用 - 适合传统业务逻辑
fun getUserSync(userId: String): User? {
return try {
restClient.get()
.uri("/users/{id}", userId)
.retrieve()
.onStatus({ it.is4xxClientError }) { _, response ->
when (response.statusCode.value()) {
404 -> throw ResourceNotFoundException("用户不存在: $userId")
else -> throw ApiException("请求失败: ${response.statusText}")
}
}
.body(object : ParameterizedTypeReference<ApiResponse<User>>() {})
?.data
} catch (ex: Exception) {
logger.error("同步获取用户失败: $userId", ex)
null
}
}
// 异步调用 - 适合高并发场景
fun getUserAsync(userId: String): Mono<User> {
return webClient.get()
.uri("/users/{id}", userId)
.retrieve()
.onStatus({ it.is4xxClientError }) { response ->
when (response.statusCode().value()) {
404 -> Mono.error(ResourceNotFoundException("用户不存在: $userId"))
else -> Mono.error(ApiException("请求失败: ${response.statusText()}"))
}
}
.bodyToMono(object : ParameterizedTypeReference<ApiResponse<User>>() {})
.map { it.data }
.doOnError { logger.error("异步获取用户失败: $userId", it) }
.onErrorReturn(null)
}
// 批量操作 - 展示响应式编程优势
fun getUsersBatch(userIds: List<String>): Flux<User> {
return Flux.fromIterable(userIds)
.flatMap { userId ->
getUserAsync(userId)
.onErrorContinue { error, _ ->
logger.warn("获取用户失败,跳过: $userId", error)
}
}
.filter { it != null }
}
}
// 配置类
@Configuration
class ApiClientConfig {
@Bean
fun restClient(builder: RestClient.Builder): RestClient {
return builder
.baseUrl("https://api.example.com")
.requestInterceptor { request, body, execution ->
// 统一添加认证和追踪信息
request.headers.apply {
add("Authorization", "Bearer ${getCurrentToken()}")
add("X-Trace-ID", MDC.get("traceId") ?: UUID.randomUUID().toString())
add("X-User-ID", getCurrentUserId())
}
val startTime = System.currentTimeMillis()
val response = execution.execute(request, body)
val duration = System.currentTimeMillis() - startTime
// 记录 API 调用指标
recordApiMetrics(request.method.name(), request.uri.path, response.statusCode.value(), duration)
response
}
.build()
}
@Bean
fun webClient(builder: WebClient.Builder): WebClient {
return builder
.baseUrl("https://api.example.com")
.filter { request, next ->
val startTime = System.currentTimeMillis()
next.exchange(
ClientRequest.from(request)
.header("Authorization", "Bearer ${getCurrentToken()}")
.header("X-Trace-ID", MDC.get("traceId") ?: UUID.randomUUID().toString())
.header("X-User-ID", getCurrentUserId())
.build()
).doOnNext { response ->
val duration = System.currentTimeMillis() - startTime
recordApiMetrics(request.method().name(), request.url().path, response.statusCode().value(), duration)
}
}
.build()
}
private fun getCurrentToken(): String {
// 从 Spring Security 或缓存中获取当前用户 token
return "mock-jwt-token"
}
private fun getCurrentUserId(): String {
// 获取当前用户 ID
return "current-user-id"
}
private fun recordApiMetrics(method: String, path: String, statusCode: Int, duration: Long) {
// 记录到监控系统 (如 Micrometer)
logger.debug("API调用指标: $method $path - $statusCode (${duration}ms)")
}
}
性能优化与最佳实践 🚀
连接池配置
kotlin
@Configuration
class PerformanceConfig {
@Bean
fun httpClientCustomizer(): ClientHttpRequestFactoryBuilderCustomizer {
return ClientHttpRequestFactoryBuilderCustomizer { builder ->
when (builder) {
is ClientHttpRequestFactoryBuilder.ApacheHttpClient -> {
builder.withHttpClientCustomizer { httpClientBuilder ->
httpClientBuilder
.setMaxConnTotal(200)
.setMaxConnPerRoute(50)
.setConnectionTimeToLive(30, TimeUnit.SECONDS)
}
}
is ClientHttpRequestFactoryBuilder.ReactorNetty -> {
builder.withHttpClientCustomizer { httpClient ->
httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.responseTimeout(Duration.ofSeconds(10))
}
}
}
}
}
}
缓存策略
kotlin
@Service
class CachedApiService(
private val restClient: RestClient,
@Qualifier("apiCache") private val cache: Cache
) {
@Cacheable(value = ["users"], key = "#userId")
fun getUserWithCache(userId: String): User? {
return restClient.get()
.uri("/users/{id}", userId)
.retrieve()
.body(User::class.java)
}
// 手动缓存控制
fun getUserWithManualCache(userId: String): User? {
val cacheKey = "user:$userId"
// 先查缓存
cache.get(cacheKey, User::class.java)?.let { return it }
// 缓存未命中,调用 API
val user = restClient.get()
.uri("/users/{id}", userId)
.retrieve()
.body(User::class.java)
// 存入缓存
user?.let { cache.put(cacheKey, it) }
return user
}
}
总结与选择指南 📋
技术选型决策树
最佳实践总结
TIP
核心建议:
- 新项目优先选择:RestClient (同步) 或 WebClient (异步)
- 统一配置管理:使用 Spring Boot 的自动配置和自定义器
- 错误处理策略:实现统一的异常处理和重试机制
- 性能优化:合理配置连接池、超时和缓存策略
- 监控和日志:添加请求追踪和性能指标收集
通过本文的深入讲解,相信你已经掌握了 Spring Boot 中 REST 客户端的核心概念和实践技巧。选择合适的工具,配置恰当的参数,你的微服务通信将更加高效和稳定! 🎉