Skip to content

Spring Boot REST 服务调用完全指南 🚀

在现代微服务架构中,服务间的 HTTP 通信是家常便饭。Spring Boot 为我们提供了三种强大的 REST 客户端工具:WebClientRestClientRestTemplate。本文将深入探讨这些工具的使用场景、核心原理以及最佳实践。

为什么需要 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) 
    }
}

三大客户端工具对比 📊

特性WebClientRestClientRestTemplate
编程模型响应式 (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 客户端优先级

  1. Reactor Netty - 默认选择,性能最佳
  2. Jetty RS Client - 适合 Jetty 环境
  3. Apache HttpClient - 功能丰富
  4. 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

核心建议

  1. 新项目优先选择:RestClient (同步) 或 WebClient (异步)
  2. 统一配置管理:使用 Spring Boot 的自动配置和自定义器
  3. 错误处理策略:实现统一的异常处理和重试机制
  4. 性能优化:合理配置连接池、超时和缓存策略
  5. 监控和日志:添加请求追踪和性能指标收集

通过本文的深入讲解,相信你已经掌握了 Spring Boot 中 REST 客户端的核心概念和实践技巧。选择合适的工具,配置恰当的参数,你的微服务通信将更加高效和稳定! 🎉