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
相关产品推荐
相关产品推荐

