springboot、微服务调用、json中文乱码问题排查
背景公司是采用微服务架构,服务即按照业务方向竖向拆分,同时同一个业务方向还按照业务层次横向拆分。其中有三个服务之间的关系如下图:A-service-1服务是A业务团队的一个比较基础的底层服务。基于springboot的2.1.5.RELEASE版本。提供了http形式的接口queryXXX提供了dubbo形式的其他接口A-service-2服务是A业务团队的一个相对上层的服务,基于springbo
背景
公司是采用微服务架构,服务即按照业务方向竖向拆分,同时同一个业务方向还按照业务层次横向拆分。其中有三个服务之间的关系如下图:
- A-service-1服务是A业务团队的一个比较基础的底层服务。基于springboot的2.1.5.RELEASE版本。
- 提供了http形式的接口queryXXX
- 提供了dubbo形式的其他接口
- A-service-2服务是A业务团队的一个相对上层的服务,基于springboot的2.1.5.RELEASE版本。依赖A-service-1的queryXXX接口,通过httpclient框架调用。
- B-service-N服务是B业务团队的一个服务,基于传统的springMVC架构。他也依赖了A-service-1中的queryXXX接口,通过httpclient框架调用。
其中A业务团队指代的就是我所在的业务团队
由于A-service-1还对外提供的dubbo形式的接口,所以用到了com.alibaba.boot的dubbo-spring-boot-starter。pom片段如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/>
</parent>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
事情的起因也正是因为dubbo,2020年6月份Apache Dubbo爆出一个高危漏洞CVE-2020-1948。
影响版本:
- Apache Dubbo 2.7.0 to 2.7.6
- Apache Dubbo 2.6.0 to 2.6.7
- Apache Dubbo all 2.5.x versions (官方已不再提供支持)
安全的版本:
- Apache Dubbo2.7.7 或更高版本
为了修复这个漏洞,不得不升级dubbo依赖,其中也是一波三折。最后为了使用Dubbo2.7.7,只能将springboot的版本提升到了当时的最新版本2.3.1.RELEASE,同时将com.alibaba.boot的dubbo-spring-boot-starter替换为了org.apache.dubbo的dubbo-spring-boot-starter,用的也是当时的最新版本2.7.7。升级之后的pom片段如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/>
</parent>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.7</version>
</dependency>
升级之后,通过单元测试service层接口、postman测试RestController接口、以及回归可能影响到A-service-2服务的所有业务功能,均正常通过。但是上线后,下游B业务方向反馈,其B-service-N服务调用A-service-1中的queryXXX接口,返回的json数据中的中文有乱码。于是紧急回滚代码,先消除影响,随后连同B业务方向的研发一起排查问题。
解决方案
毕竟服务A-service-1在未升级springboot和dubbo版本之前,是正常提供服务的,没有乱码问题的。升级之后出现了乱码,可以肯定和springboot或者dubbo的版本升级有关。但是:
- 漏洞是必须堵的,所以版本升级是没得选的。
- 乱码问题也是必须解决的,又不能通过降版本解决,只能寻求其他解决方案。
于是在度娘上线寻求答案。
很多文章都提到了一个大前提,解决乱码问题,要先约定编码格式,也就是各个开发团队、微服务之间的数据传输格式都要采用相同的编码格式。
对于我的场景而言,使用的编码肯定是UTF-8,数据传输协议都是基于json格式,这是开发约定。
底层框架(spring、httpclient)对于编码的转换一般都替程序员做了比较好的适配,导致我们在业务开发层面,不会很刻意的去设置编码格式。
关于springboot乱码相关的帖子和文章有很多,总结了一下其中的解决方案大体分为3种:
1、设置spring全局的编码格式为UTF-8(我的场景里不起作用)
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8
2、通过设置@RequestMapping的produces,把content-type的charset设置为utf-8(我最开始使用的方式,能够解决问题)
@RequestMapping(value = "queryXXX", produces = "application/json;charset=UTF-8")
3、自定义、扩展converter(我最后使用的方式)
converter扩展,还有两种方案:
- 继承WebMvcConfigurerAdapter,spring5.0之后被标记为废弃,不建议使用
- 继承WebMvcConfigurationSupport。
我的A-service-1里用的是springboot2.3.1、他对应的spring版本是5.2.7。所以肯定是要基于继承WebMvcConfigurationSupport来进行converter扩展。
1)我最先尝试的方式(未能解决问题)(是因为没有关注到我自己服务的特性,这种方案可解决普通文本乱码问题)
@Configuration
public class MyWebMvcConfig extends WebMvcConfigurationSupport {
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
return new StringHttpMessageConverter(Charset.forName("UTF-8"));
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(responseBodyConverter());
}
}
2)我最后使用的方式(在我分析了相关的源码后)(完美解决问题)
@Configuration
public class MyWebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
// 解决controller返回普通文本中文乱码问题
if (converter instanceof StringHttpMessageConverter) {
((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
}
// 解决controller返回json对象中文乱码问题
if (converter instanceof MappingJackson2HttpMessageConverter) {
((MappingJackson2HttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
}
}
}
}
解决方案分析
上面提到了,我的第一版解决方案是通过在接口的RequestMapping注解上设置produces属性解决的
@RequestMapping(value = "queryXXX", produces = "application/json;charset=UTF-8")
但这种方式有个不友好的地方,就是服务中所有RestController中的接口都需要这么设置,要么给每个Controller的中的每个接口方法配置,要么给每个Controller全局配置。总之所有的Controller都要改动一下。感觉这种解决方案不是很友好。
所以一开始并没有想通过这种方式解决,先尝试了继承WebMvcConfigurationSupport、覆写configureMessageConverters方法的方式。
1、这个方案当时是直接从网上的帖子摘下来的,由于并没有深入思考自己代码的特性,直接照搬,然后运行无效果,乱码依旧。
2、这个方案只是没有解决我的服务的问题,但是是可以解决其他场景问题的。
@Configuration
public class MyWebMvcConfig extends WebMvcConfigurationSupport {
/*
定义一个HttpMessageConverter类型的bean,返回一个StringHttpMessageConverter实例。
这个StringHttpMessageConverter是针对内容类型(content-type)为普通文本(text/plain)类型的数据进行转换处理的。
他实现了HttpMessageConverter接口
*/
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
return new StringHttpMessageConverter(Charset.forName("UTF-8"));
}
/*
覆写configureMessageConverters方法,
先调用父类的同名同参方法,保证公共逻辑不会丢失,
然后再把自己定义的StringHttpMessageConverter实例添加到converters集合中,
这样就可以保证自己定义的StringHttpMessageConverter排在所有converter之前,
因为在他之后,还会往converters里面添加很多其他内容类型(json、byte数组、xml)的默认的converter,
其中就当然也包括对于普通文本(text/plain)进行处理的默认的StringHttpMessageConverter实例。
所以这里的converters里面会存在两个StringHttpMessageConverter类型的对象,
一个是我们自定义的UTF-8的StringHttpMessageConverter,他排在converters列表中的第一个位置,
一个是框架创建的默认的,他内置的默认编码格式是ISO_8859_1,但他在列表中的排位靠后。
所以一个普通文本类报文首先会被我们定义的UTF-8的StringHttpMessageConverter处理掉,然后就return,
其他后续的converter就轮不到处理机会了。
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(responseBodyConverter());
}
}
通过上面的源码解析,实际上我们也就了解到了,之所以这个没有解决我的问题,是因为我的queryXXX接口按照开发约定返回json格式的数据,返回数据的内容类型基于application/json格式,并非text/plain格式。所以自定义的StringHttpMessageConverter并不会处理我的数据。而针对application/json的内容类型,是由MappingJackson2HttpMessageConverter类来处理的。
如果按照以上的代码套路,所以我们只需要定义一个UTF-8编码格式的MappingJackson2HttpMessageConverter实例添加到converters就可以了。但是spring5.2.7中MappingJackson2HttpMessageConverter并没有提供基于Charset的构造方法,但是我们还是可以通过setDefaultCharset来指定默认的编码格式。
既然只能通过setDefaultCharset来指定默认的编码格式,那我们是否还真得有必要去创建一个MappingJackson2HttpMessageConverter实例么?
我们在上面源码分析的注释中提到,spring框架是会针对每种内容类型都会加载一个默认的HttpMessageConverter实例的。也就是框架层面能够保证convertors里面一定会存在一个MappingJackson2HttpMessageConverter实例,我们只要修改这个实例的defaultCharset就可以了。所以就有了下面的这种解决方案。
@Configuration
public class MyWebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
// 解决controller返回普通文本中文乱码问题
if (converter instanceof StringHttpMessageConverter) {
((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
}
// 解决controller返回json对象中文乱码问题
if (converter instanceof MappingJackson2HttpMessageConverter) {
((MappingJackson2HttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
}
}
}
}
这次我们覆写的是extendMessageConverters方法,通过循环遍历,找到我们需要的converter,然后通过调用setDefaultCharset方法去更改他的defaultCharset。
到这里你应该会有疑问,同样是继承WebMvcConfigurationSupport,怎么能知道到底覆写哪个方法能实现目的?
这就需要再分析一下WebMvcConfigurationSupport类中和getMessageConverters方法相关的源码。
/**
* Provides access to the shared {@link HttpMessageConverter HttpMessageConverters}
* used by the {@link RequestMappingHandlerAdapter} and the
* {@link ExceptionHandlerExceptionResolver}.
* <p>This method cannot be overridden; use {@link #configureMessageConverters} instead.
* Also see {@link #addDefaultHttpMessageConverters} for adding default message converters.
*
* 这个方法为RequestMappingHandlerAdapter类和ExceptionHandlerExceptionResolver类提供了访问共享的HttpMessageConverter的能力
* RequestMappingHandlerAdapter的职责就包括对于@RequestMapping注解的解析处理能力
* ExceptionHandlerExceptionResolver的职责就包括对于@ExceptionHandler注解的解析处理能力
* 这个方法不能被覆写(因为标记了final关键字), 但是你可以通过覆写configureMessageConverters来达到一些增强的目的。
* addDefaultHttpMessageConverters定义了convertors里都添加哪些默认converter。
* 覆写extendMessageConverters方法,可以针对已经所有converter(这时候自定义的和默认的都已经在converters集合里了)进行一些后置处理。(这也是我采用的)
*
*/
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) { // 先判空,不为空就直接返回了
this.messageConverters = new ArrayList<>(); // 为空的话,new一个列表
configureMessageConverters(this.messageConverters); // 这个方法留给子类扩展,因为本类中就是个空实现
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters); // 添加默认的各种converter
}
extendMessageConverters(this.messageConverters); // 这个方法留给子类扩展,因为本类中就是个空实现
}
return this.messageConverters;
}
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
// 篇幅原因,这里面的实现代码没有贴出来
// 这个方法里面就是针对各种报文类型,把对应的默认的converter添加到messageConverters中
}
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
通过以上的源码分析,我们可以总结如下:
- 可以通过覆写configureMessageConverters或者extendMessageConverters方法来扩展一下HttpMessageConverter的能力。
- configureMessageConverters是前置方法,通过这个方法可以添加自己的针对特定内容类型的实例,则可以达到优先级最高,等同于屏蔽掉列表中其他同类型实例。
- extendMessageConverters是后置方法,在这里可以针对框架自带的默认的各种类型的HttpMessageConverter实例进行一些属性上的自定义设置。
问题产生的原因分析
在开始部分,我们介绍了问题产生的背景。我们再来串联一个这个事情。
- A-service-1对A-service-2和B-service-N提供了http形式的queryXXX接口。
- A-service-1服务为了堵dubbo漏洞,升级了springboot和dubbo,并没有任何其他变更,升级后:
- A-service-2服务调用queryXXX接口,返回正常的json数据。
- B-service-N服务调用queryXXX接口,返回的json数据中,中文乱码。
- A-service-1回滚之后B-service-N恢复正常。
从A-service-1的两个版本差异分析
问题体现在B-service-N服务中,所以我们在B-service-N服务分别对A-service-1的两个版本代码进行调用,在httpclient层抓取响应信息进行对比。我们发现了差异。
-
A-service-1在springboot2.1.5版本时的response如下图
-
A-service-1在springboot2.3.1版本时的response如下图
差异在content-type上,2.1.5版本中的content-type包含了charset为UTF-8,而2.3.1版本中没有包含charset信息。
通过对spring源码分析,spring针对http请求,任何一种HttpMessageConverter在处理完http请求,对于响应信息,都会针对http报文设置header中的属性。具体实现在抽象类
AbstractHttpMessageConverter中的addDefaultHeaders方法.
protected void addDefaultHeaders(HttpHeaders headers, T t, @Nullable MediaType contentType) throws IOException {
if (headers.getContentType() == null) {
MediaType contentTypeToUse = contentType; // 定义一个局部的MediaType,先用外部传来的contentType赋值给他
// 省略了部分代码
if (contentTypeToUse != null) { // 如果不为空,说明传进来的contentType不为空
if (contentTypeToUse.getCharset() == null) { // 从contentType中获取charset属性,如果没有获取到
/*
重点在这行代码!!!!!
getDefaultCharset()获取的就是在该类中定义的一个实例属性defaultCharset(可以被子类继承的)
源码:@Nullable private Charset defaultCharset;
该defaultCharset在本类中只声明,未赋值,可为空,因为AbstractHttpMessageConverter是抽象类,不能被实例化,
所以这个属性是需要子类通过调用setDefaultCharset来进行设置的。
如果子类没有通过调用setDefaultCharset来显示设置该属性,那么子类通过getDefaultCharset()获取到仍然为空。
*/
Charset defaultCharset = getDefaultCharset();
if (defaultCharset != null) {
/*
如果defaultCharset不为空,根据一个不包含charset的contentType对象和默认的defaultCharset构造一个新的MediaType对象,这个对象就包含了charset了
如果能走到这行代码,就等同于进行了如下流程:
原来的http报文中header中的content-type是"application/json"
根据这个"application/json"可以找到他对应的converter处理类
然后获取到这个处理类设置的defaultCharset,假设是UTF-8
再重新设置content-type为"application/json;charset=UTF-8"
*/
contentTypeToUse = new MediaType(contentTypeToUse, defaultCharset);
}
}
// 把contentTypeToUse设置给headers的ContentType属性
headers.setContentType(contentTypeToUse);
}
}
// 省略了部分代码
}
接下来就来看下不同版本中的这个defaultCharset的逻辑差异。
2.1.5版本的springboot对应的spring版本是5.1.7:
2.3.1版本的springboot对应的spring版本是5.2.7
我们的请求都是基于"application/json"的内容形式,前面也提到过这种类型的http请求对应的converter是MappingJackson2HttpMessageConverter。而MappingJackson2HttpMessageConverter上层还有一个抽象类是AbstractJackson2HttpMessageConverter。这个抽象类在两个版本中的构造函数实现上存在一些差异。
1、spring5.1.7中的实现
/**
* The default charset used by the converter.
*/
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
setDefaultCharset(DEFAULT_CHARSET); // 重点是这行
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
this.ssePrettyPrinter = prettyPrinter;
}
因为在构造MappingJackson2HttpMessageConverter类的实例的时候一定会调用父类的对应构造方法,就会调用setDefaultCharset方法,给MappingJackson2HttpMessageConverter的实例的defaultCharset设置成StandardCharsets.UTF_8。
2、spring5.2.7中的实现如下:
/**
* The default charset used by the converter.
*/
@Nullable
@Deprecated
public static final Charset DEFAULT_CHARSET = null;
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
this.ssePrettyPrinter = prettyPrinter;
}
在这一般的实现中DEFAULT_CHARSET常量虽然被保留,但是赋值为null。同时被标记上了@Deprecated注解,标识要废弃掉。而且构造函数内部实现也没有了setDefaultCharset调用,这就意味着缺少了在content-type中补全charset的能力。给框架使用者我的感觉就是:版本升级了,功能变脆了。期待以后的版本能把这个问题优化下。
找到了引起问题的差异所在,但是升级不可避免。
所以针对这个问题的两种解决方案
- produces = “application/json;charset=UTF-8”
- 调用MappingJackson2HttpMessageConverter实例setDefaultCharset(UTF-8)
本质上都是为了在content-type信息中补充上charset信息。明确指定我报文内容的编码就是UTF-8,你请求方拿到之后,按照我的编码方式去解码即可,也不用瞎猜编码了。
上面都是从A-service-1的角度出发,找到了A-service-1上的解决方法,那么如果从B-servic-N角度寻求解决方案的话是否可行呢?因为A-service-2同样也调用A-service-1的同一个接口,在content-type里没有charset的情况下,也并没有发生乱码问题。
从A-service-2和B-service-N的差异分析
A-service-2和B-service-N调用的都是A-service-1的同一个接口,采用的技术框架都是httpclient,但是A-service-2并没有产生乱码问题,但是B-service-N却产生了。
上文中已经提到过,对于A-service-1在springboot2.3.1版本时的返回的response如下图
通过排查分析A-service-2服务和B-service-N服务在调用外部接口层面的差异点。
最终得出结论,是因为两个服务所使用的httpclient版本并不相同。
- A-service-2服务使用httpclient版本为4.5.8(对应httpcore版本4.4.11)
- B-service-N服务使用httpclient版本为4.5.1(对应httpcore版本4.4.1)
两个版本在针对于http请求的response进行编码处理时存在一些差异。主要差异在
org.apache.http.util.EntityUtils类的实现上。
【4.4.1的httpcore中的实现】
public static String toString(final HttpEntity entity) throws IOException, ParseException {
return toString(entity, (Charset)null); // 调用下面的toString方法,这里传入的Charset为空
}
public static String toString(
final HttpEntity entity, final Charset defaultCharset) throws IOException, ParseException {
// 省略了部分代码
try {
// 省略了部分代码
Charset charset = null;
try {
/*
根据HttpEntity实例构造一个ContentType对象。
ContentType有三个属性,也就是说要从HttpEntity中摘取出这三部分信息。
private final String mimeType;
private final Charset charset;
private final NameValuePair[] params;
我们中点关注的是mimeType和charset,这两个属性就在http报文的header中的content-type属性中提取
假设http报文中content-type的值为"application/json;charset=UTF-8",则获取到的mimeType为application/json,charset就为UTF-8
假设http报文中content-type的值为"application/json",则获取到的mimeType为application/json,charset就为null
所以即使contentType不为空,那么他的charset也可能为null。
*/
final ContentType contentType = ContentType.get(entity);
if (contentType != null) {
charset = contentType.getCharset();
}
} catch (final UnsupportedCharsetException ex) {
if (defaultCharset == null) {
throw new UnsupportedEncodingException(ex.getMessage());
}
}
if (charset == null) { // 如果charset为空,说明从content-type中没获取到
// 那么就采用入参defaultCharset,但是这个defaultCharset传入的是null
charset = defaultCharset;
}
// 到这里还为空,就说明content-type中没获取到,参数传进来的defaultCharset也是null
if (charset == null) {
// 那么就使用HTTP.DEF_CONTENT_CHARSET这个编码常量了,他是ISO_8859_1
// 所以一旦走到这一步,如果报文的的body内容确实采用UTF-8编码,但是报文header的content-type中没有说明用的什么编码格式,那么就会采用ISO_8859_1来解码了,所以就会乱码。
charset = HTTP.DEF_CONTENT_CHARSET;
}
// 省略了部分代码
} finally {
instream.close();
}
}
【4.4.11的httpcore中的实现】
public static String toString(final HttpEntity entity) throws IOException, ParseException {
Args.notNull(entity, "Entity");
return toString(entity, ContentType.get(entity)); // 这里传入的是一个ContentType实例,需要根据根据HttpEntity中的content-type属性信息来构造一个ContentType对象。重点处理逻辑在下面的toString方法中。
}
private static String toString(
final HttpEntity entity,
final ContentType contentType) throws IOException {
// 省略了部分代码
try {
// 省略了部分代码
Charset charset = null; // 定义一个编码变量
if (contentType != null) {
/*
从contentType中获取charset(结合4.4.1中toString中的注释分析)
举例:
假设http报文中content-type的值为"application/json;charset=UTF-8",则获取到的charset就为UTF-8
假设http报文中content-type的值为"application/json",则获取到的charset就为null
*/
charset = contentType.getCharset();
/*
如果获取到的charset为null,说明content-type中并没有指定charset
那么就根据content-type中内容类型,取一个为这个类型提前预定义的一个默认的charset。
如果http报文中content-type为"application/json",
那么contentType.getMimeType()返回的就是"application/json"
那么通过ContentType.getByMimeType("application/json")的返回结果是APPLICATION_JSON
源码:public static final ContentType APPLICATION_JSON = create("application/json", Consts.UTF_8);
这个APPLICATION_JSON是一个预置的ContentType实例,他的getCharset()自然就返回UTF-8
所以只要http报文中正确的设置了content-type的mimetype,即使没有额外指明charset。也可以根据mimetype推导出一个默认的编码。
*/
if (charset == null) {
final ContentType defaultContentType = ContentType.getByMimeType(contentType.getMimeType());
charset = defaultContentType != null ? defaultContentType.getCharset() : null;
}
}
if (charset == null) {
charset = HTTP.DEF_CONTENT_CHARSET;
}
// 省略了部分代码
} finally {
inStream.close();
}
}
对比了两个版本的实现后,我们发现,4.4.11的版本比4.4.1的版本多了一层编码防护,也就是根据conten-type的mimeType推导一个默认编码。这样即便是conten-type中没有指定编码格式,也可以采用的mimeType对应的默认编码,可以很大程度减少解码时的不一致。
再回到我们的场景中,因为B-service-N使用的httpclient版本相对较低,导致了对于content-type没有指定charset的http报文的编码处理会统一采用ISO-8859-1,数据传输都是以字节为单位,数据内容也都是基于特定编码转换成字节的,如果数据是以UTF-8编码的字节数组,用ISO-8859-1解码成字符串,那自然就会出现乱码问题(中文)。
虽然发现了B-service-N的问题所在,也可以通过升级B-service-N的httpclient解决该问题。
但是从工程、业务、团队等多个角度讲,因为A-service-1的升级springboot导致了乱码,未升级前并没有产生乱码,所以这个锅B-service-N不会背,还会要甩回给A-service-1,从广义上讲可以认为是A-service-1单方更改了接口协议(虽然业务数据协议没变化,但是http协议有变化了)
总结
从纯技术层面上来看,解决这个编码问题,从两个维度均可。
- 服务的提供方来完善补全自己的charset信息。
- 服务的调用方对charset信息进行推导。
但从工程设计角度来讲,肯定是首选第一种,第一种就好比商家即提供产品(数据)也提供说明书(编码)。第二种就相当于客户只拿到了产品(数据),没有说明(编码),要自己凭经验摸索怎么用(按自己的规则拍个编码)。
更多推荐
所有评论(0)