SpringBoot2.3.9 乱码问题分析解决

由于业务需求,我们需要将我们原本的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> getMessageConverters() {
        if (this.messageConverters == null) {
            this.messageConverters = new ArrayList>();
            configureMessageConverters(this.messageConverters);
            if (this.messageConverters.isEmpty()) {
                addDefaultHttpMessageConverters(this.messageConverters);
            }
            extendMessageConverters(this.messageConverters);
        }
        return this.messageConverters;
    }
    ......
    protected final void addDefaultHttpMessageConverters(List> messageConverters) {
        ......
        if (jackson2Present) {
            ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build();
            messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));
        }
        ......
    }
}

public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter {
    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 implements HttpMessageConverter {
    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 {
    /**
     * 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> converters) {
    for (HttpMessageConverter converter : converters) {
      if (converter instanceof MappingJackson2HttpMessageConverter) {
        ((MappingJackson2HttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
     }
    }
  }
}

针对SpringBoot2.3.9 => Spring-Web5.2版本的乱码问题,这里也做了产生的原因和修改的方式,这里笔者使用的方式一,因为方式二是针对的具体某一种HttpMessageConverter类型做的设置默认编码格式,具体使用哪一种方式根据各自业务情况决定!

你可能感兴趣的:(SpringBoot2.3.9 乱码问题分析解决)