FeignException错误信息中撇号被转义为\u0027问题排查
Feign对接SonarQube时异常信息单引号被转义为\u0027问题解决
问题场景
使用Feign客户端对接SonarQube接口拉取数据,在测试传入错误组件名的异常场景时,发现异常信息中的撇号(单引号)被自动转义为Unicode编码\u0027。
- 项目依赖版本:
spring-cloud-starter-openfeign:3.0.1 - 直接通过浏览器访问对应SonarQube接口,返回的JSON错误信息使用普通单引号,可确认转义逻辑发生在Feign处理环节。
- 问题影响:单元测试断言异常消息时,匹配带普通单引号的字符串会失败,只有匹配带
\u0027转义序列的字符串才能通过。
复现代码
void measuresSearchHistoryUnknownTest() { assertThatExceptionOfType(FeignException.NotFound.class) .isThrownBy(() -> sonarFeignClient.measuresSearchHistory( "badProject", String.join(",", EnumUtils.getJsonValue(SonarMetric.BLOCKER_VIOLATIONS), EnumUtils.getJsonValue(SonarMetric.CRITICAL_VIOLATIONS) ), 1, 10 )).withMessageContaining("Component key 'badProject' not found"); // )).withMessageContaining("Component key \\u0027badProject\\u0027 not found"); }
报错日志
Expecting throwable message: <"[404] during [GET] to [*******/sonar/api/measures/search_history?component=badProject&metrics=blocker_violations%2Ccritical_violations&p=1&ps=10] [SonarFeignClient#measuresSearchHistory(String,String,int,int)]: [{"errors":[{"msg":"Component key \u0027badProject\u0027 not found"}]}]"> to contain: <"Component key 'badProject' not found"> but did not. Throwable that failed the check: feign.FeignException$NotFound: [404] during [GET] to [*******/sonar/api/measures/search_history?component=badProject&metrics=blocker_violations%2Ccritical_violations&p=1&ps=10] [SonarFeignClient#measuresSearchHistory(String,String,int,int)]: [{"errors":[{"msg":"Component key \u0027badProject\u0027 not found"}]}]
根因
Spring Cloud OpenFeign 3.0.1版本绑定的feign-core核心包,在构造Feign异常对象、拼接错误响应体到异常消息时,默认使用的JSON序列化逻辑会对单引号等特殊字符做Unicode转义,没有保留原始字符格式。
解决方案
方案1:自定义Feign错误解码器(推荐)
自定义ErrorDecoder替换默认实现,读取错误响应体后主动替换转义字符,保证异常消息格式和接口原始返回一致:
import feign.FeignException; import feign.Response; import feign.Util; import feign.codec.ErrorDecoder; import org.springframework.context.annotation.Bean; import java.nio.charset.StandardCharsets; public class FeignConfig { @Bean public ErrorDecoder feignErrorDecoder() { return (methodKey, response) -> { String body = Util.toString(response.body().asReader(StandardCharsets.UTF_8)); // 替换转义的单引号为原始字符 String unescapedBody = body.replace("\\u0027", "'"); String message = String.format("[%d] during [%s] to [%s] [%s]: [%s]", response.status(), response.request().httpMethod().name(), response.request().url(), methodKey, unescapedBody); if (response.status() == 404) { return new FeignException.NotFound(message, response.request(), unescapedBody.getBytes(StandardCharsets.UTF_8), response.headers()); } return new FeignException(response.status(), message, response.request(), unescapedBody.getBytes(StandardCharsets.UTF_8), response.headers()); }; } }
将该配置类指定到对应的FeignClient上即可生效:
@FeignClient(name = "sonarClient", url = "${sonar.url}", configuration = FeignConfig.class) public interface SonarFeignClient { // 接口方法定义 }
注意:自定义异常构造时需要传入原始响应体的字节数组,避免后续逻辑需要读取错误响应内容时出现流已关闭、内容为空的问题。
方案2:全局配置Jackson关闭字符转义
如果项目中所有Feign客户端都需要关闭这类转义,可以直接全局配置Feign使用的ObjectMapper,禁用特殊字符的Unicode转义规则:
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @Bean @Primary public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); // 关闭非ASCII字符转义 objectMapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, false); return objectMapper; }
临时方案(仅适配测试场景)
如果不需要修改生产代码的异常逻辑,可以在单元测试中先对异常消息做转义替换后再做断言,不需要匹配转义后的字符串。
内容的提问来源于stack exchange,提问作者Roma Roma
相关产品推荐
相关产品推荐

