由于业务需求,我们需要将我们原本的SpringBoot2.0.4版本升级到SpringBoot2.3.9版本,经过我们不懈努力,我们基础库适配了SpringBoot2.3.9版本,各种Mock之后,我们决定进行第一轮SpringBoot2.3.9版本基础库的检验,检验我们的SpringBoot2.3.9基础库是否有无问题,现实是残酷的,
我们升级了SpringBoot2.3.9版本的服务经过Zuul网关发现全部乱码了,不经过网关就一切正常
,所以本节我们就分析下乱码的原因以及解决的方法。
我们的基础框架中在基于SpringBoot2.0.4和SpringBoot2.3.9版本中都是使用的JackSon作为数据序列化和反序列化框架,我们在适配SpringBoot2.3.9版本时候,也没有对JackSon做其他扩展改动,但是为什么就乱码了呢?所以围绕这个问题,既然乱码,那肯定就是编码格式问题,围绕这个问题点,我决定对比下SpringBoot2.0.4和SpringBoot2.3.9版本的JackSon相关设置,入口配置类HttpMessageConvertersAutoConfiguration
,其中会设置HttpMessageConverters
SpringBoot2.0.4
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
......
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
......
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
......
if (jackson2Present) {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build();
messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));
}
......
}
}
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
......
protected void init(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
setDefaultCharset(DEFAULT_CHARSET);
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
this.ssePrettyPrinter = prettyPrinter;
}
......
}
public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
protected void addDefaultHeaders(HttpHeaders headers, T t, MediaType contentType) throws IOException{
if (headers.getContentType() == null) {
MediaType contentTypeToUse = contentType;
......
if (contentTypeToUse != null) {
if (contentTypeToUse.getCharset() == null) {
Charset defaultCharset = getDefaultCharset();
if (defaultCharset != null) {
contentTypeToUse = new MediaType(contentTypeToUse, defaultCharset);
}
}
headers.setContentType(contentTypeToUse);
}
}
......
}
}
在SpringBoot2.0.4版本中,我们可以看出在AbstractJackson2HttpMessageConverter这个初始化过程中会设置默认的编码格式UTF-8,所以我们在addDefaultHeaders这个方法中,最终设置的headers.setContentType(contentTypeToUse);这个格式会是MediaType#APPLICATION_JSON_UTF8 => application/json;charset=UTF-8
SpringBoot2.3.9
在SpringBoot2.3.9版本中,WebMvcConfigurationSupport、AbstractHttpMessageConverter变化都不大,基本都是一样的逻辑,唯一有改动的地方为AbstractJackson2HttpMessageConverter这个类,所以我们重点看这个
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
/**
* 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;
}
......
}
我们看见初始化AbstractJackson2HttpMessageConverter这个类中直接删掉了设置默认编码格式,官方给的解释大致为:认为这个默认编码已经不需要设置了,因为现在绝大多数的浏览器或者框架都默认会设置编码格式!
,所以我们在回头看看如果没有这个默认编码格式,那么最终的headers.setContentType(contentTypeToUse);这个会变成什么呢?变成MediaType#APPLICATION_JSON => application/json 这样的格式会发生什么呢?我们看看经过网关Zuul会有什么现象!
网关Zuul分析
public class DispatcherServlet extends FrameworkServlet {
......
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
......
//这个就会调用Zuul的run()
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
......
}
......
}
Zuul的route阶段会请求下游服务获取数据,经过Debug发现在Zuul=>route阶段请求下游服务获取出来的数据编码格式均为utf-8,然后Zuul=>post数据包装阶段数据也为正常的utf-8,route、post阶段获取出来的数据都没有乱码,那么说明Zuul的请求和包装阶段是正常的,继续跟踪后发现DispatcherServlet#doDispatch最终返回的Response数据中characterEncoding格式为ISO-8859-1的格式,此时我们最终的Response数据如果有中文,那么就会变成一堆❓❓❓这样的问号。Zuul相关原理可参阅Spring Cloud Zuul 分析(三)之ZuulFilter调用过程
解决方式
#方式一
server:
servlet:
encoding:
charset: utf-8
#只设置Response
#HttpEncodingAutoConfiguration#CharacterEncodingFilter中会使用forceResponse
#CharacterEncodingFilter#doFilterInternal中会设置HttpServletResponse.setCharacterEncoding
forceResponse: true
enabled: true
#方式二
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
((MappingJackson2HttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
}
}
}
}
针对SpringBoot2.3.9 => Spring-Web5.2版本的乱码问题,这里也做了产生的原因和修改的方式,这里笔者使用的方式一,因为方式二是针对的具体某一种HttpMessageConverter类型做的设置默认编码格式,具体使用哪一种方式根据各自业务情况决定!