You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring RestOperations调用API时HttpStatusCodeException无法捕获问题

问题根源

当API返回4xx错误时,你使用的postForObject方法会强制将所有响应(包括错误响应)解析为SignupResponse类型,但错误响应的JSON结构并不包含user_id字段,直接导致解析失败抛出HttpMessageNotReadableException——这个异常会先于HttpStatusCodeException触发,因此无法进入你预期的异常分支。

另外,你自定义的CustomResponseErrorHandler未生效,核心原因是没有将其绑定到你的RestOperations实例(比如RestTemplate)上。

解决方案

方案1:用exchange替代postForObject,手动处理响应状态与解析

exchange方法允许先获取完整的ResponseEntity,可以先判断状态码,再根据场景解析响应体,避免错误响应被强制解析为成功类型:

override fun signup(request: SignupRequest, token: String): SignupResponse {
    val headers = HttpHeaders()
    headers.contentType = MediaType.APPLICATION_JSON
    headers.setBearerAuth(token)

    val entity = HttpEntity(request, headers)

    try {
        val response = restClient.exchange(
            "$apiUrl/api/users",
            HttpMethod.POST,
            entity,
            String::class.java // 先以字符串形式获取响应体
        )
        
        return when (response.statusCode) {
            HttpStatus.OK -> objectMapper.readValue(response.body, SignupResponse::class.java)
            HttpStatus.CONFLICT -> throw APIException("用户已存在")
            else -> throw APIException(readErrorResponse(response.body))
        }
    } catch (e: HttpStatusCodeException) {
        throw APIException(readError(e))
    } catch (e: Exception) {
        throw UnauthorizedException(HttpStatus.INTERNAL_SERVER_ERROR)
    }
}

方案2:配置自定义ResponseErrorHandler并绑定到RestOperations

自定义错误处理器,确保检测到4xx/5xx状态码时优先处理错误响应,避免解析异常提前抛出:

步骤1:完善自定义错误处理器

class CustomResponseErrorHandler(private val objectMapper: ObjectMapper) : ResponseErrorHandler {
    override fun hasError(response: ClientHttpResponse): Boolean {
        // 识别所有4xx/5xx状态码为错误
        return response.statusCode.is4xxClientError || response.statusCode.is5xxServerError
    }

    override fun handleError(response: ClientHttpResponse) {
        val statusCode = response.statusCode
        // 读取错误响应体
        val errorBody = response.body.bufferedReader().use { it.readText() }
        
        // 根据状态码抛出对应自定义异常
        val apiException = APIException("请求失败: $statusCode, 详情: $errorBody")
        when (statusCode) {
            HttpStatus.CONFLICT -> throw apiException
            else -> throw apiException
        }
    }
}

步骤2:绑定错误处理器到RestTemplate

在配置类中注入RestTemplate时设置错误处理器:

@Configuration
class RestClientConfig {
    @Bean
    fun restTemplate(objectMapper: ObjectMapper): RestTemplate {
        val restTemplate = RestTemplate()
        restTemplate.errorHandler = CustomResponseErrorHandler(objectMapper)
        return restTemplate
    }
}

步骤3:修改业务代码

此时postForObject会在检测到错误响应时触发自定义处理器,直接抛出你定义的APIException:

override fun signup(request: SignupRequest, token: String): SignupResponse {
    val headers = HttpHeaders()
    headers.contentType = MediaType.APPLICATION_JSON
    headers.setBearerAuth(token)

    val entity = HttpEntity(request, headers)

    return try {
        restClient.postForObject("$apiUrl/api/users", entity, SignupResponse::class.java)!!
    } catch (e: APIException) {
        throw e
    } catch (e: Exception) {
        throw UnauthorizedException(HttpStatus.INTERNAL_SERVER_ERROR)
    }
}
关键说明
  • postForObject会自动尝试解析所有响应体为指定类型,不适合需要区分成功/错误响应结构的场景,exchange的灵活性更强。
  • 自定义ResponseErrorHandler必须绑定到RestOperations实例才会生效,这是你之前自定义处理器未触发的核心原因。

内容的提问来源于stack exchange,提问作者Raghav Manikandan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 09:33:37