美文网首页
RestTemplate 调用中文乱码

RestTemplate 调用中文乱码

作者: Always_July | 来源:发表于2022-05-18 09:29 被阅读0次

背景

服务A通过RestTemplate调用服务B,服务B升级后,出现了中文乱码。

框架版本

服务A spring-web 版本

 <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.0.7.RELEASE</version>
      <scope>compile</scope>
    </dependency>

服务B spring-web版本

 <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
     <!-- 升级前<version>4.3.19.RELEASE</version>-->
      <version>5.0.7.RELEASE</version>
      <scope>compile</scope>
    </dependency>

服务A的调用代码

String result = restTemplate.postForObject(url, formEntity, String.class);

服务B代码

    @PostMapping(value = "/getSmsTemplateList")
    @ResponseBody
    public PageDTO<SmsTemplateDTO> getSmsTemplateList(@RequestBody PageQueryDTO pageQueryDTO) {
        return smsTemplateService.querySmsTemplateList(pageQueryDTO);
    }

Debug 看A服务的Response 解码

调用堆栈

  1. org.springframework.web.client.RestTemplate#postForObject(java.lang.String, java.lang.Object, java.lang.Class<T>, java.lang.Object...)

  2. org.springframework.web.client.RestTemplate#execute(java.lang.String, org.springframework.http.HttpMethod, org.springframework.web.client.RequestCallback, - org.springframework.web.client.ResponseExtractor<T>, java.lang.Object...)

  3. org.springframework.web.client.RestTemplate#doExecute


    image.png

解析response代码

org.springframework.web.client.HttpMessageConverterExtractor#extractData


image.png

debug 看到使用的是 StringHttpMessageConvert

StringHttpMessageConvert 解析数据

org.springframework.http.converter.StringHttpMessageConverter#readInternal

@Override
    protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
       //1
        Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
       //2
        return StreamUtils.copyToString(inputMessage.getBody(), charset);
    }

// 1 处是先从response的content-type获取 charset,然后//2出解析字符串,我debug 此处发现response的content-type 没有传入charset,然后使用的charset的是 StandardCharsets.ISO_8859_1。

查看org.springframework.http.converter.StringHttpMessageConverter的构造方法,默认的是ISO_8859_1。

      public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;

    /**
     * A default constructor that uses {@code "ISO-8859-1"} as the default charset.
     * @see #StringHttpMessageConverter(Charset)
     */
    public StringHttpMessageConverter() {
        this(DEFAULT_CHARSET);
    }

服务A解决乱码的方法

核心是设置StringHttpMessageConverter的charset。

配置RestTemplate

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory){
        RestTemplate restTemplate = new RestTemplate(factory);

        List<HttpMessageConverter<?>> messageConverterList =  restTemplate.getMessageConverters();
        Iterator<HttpMessageConverter<?>> iterator = messageConverterList.iterator();
        while ( iterator.hasNext()){
            HttpMessageConverter<?> converter = iterator.next();
            // 原有的String是ISO-8859-1编码 ,设置为UTF-8
            if (converter instanceof StringHttpMessageConverter) {
                ((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
                //iterator.remove();
                break;
            }
        }
        return restTemplate;
    }

到这里已经可以解决问题,但是为什么服务B升级后会导致乱码的,看服务A的源码可以猜测,服务B升级前返回的content-type 里面有charset,而升级后没有了,导致StringHttpMessageConverter使用的默认的charset。

可以看到,很简单,只是一个分页查询,下面我们来看看如何输出 response的。

B服务的源码解析

升级前的源码

查看选择了哪个HttpMessageConvert

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)

image.png

选择的是org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

看看具体write方法

org.springframework.http.converter.AbstractGenericHttpMessageConverter#write

image.png

contentTypeToUse 为application/json ,但是没有设置charset,使用默认的charset,此时默认的
默认的charset是UTF-8,所以response header里的content-type 是application/json;charset=UTF-8。

看一下这个AbstractGenericHttpMessageConverter 的default charset什么时候设置的

在 方法初始化设置的 org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#init

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType... supportedMediaTypes) {
        super(supportedMediaTypes);
        init(objectMapper);
    }

    protected void init(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
         // 设置默认的charset
        setDefaultCharset(DEFAULT_CHARSET);
        DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
        prettyPrinter.indentObjectsWith(new DefaultIndenter("  ", "\ndata:"));
        this.ssePrettyPrinter = prettyPrinter;
    }

升级后的源码

升级spring版本后 处理流程基本类似,核心还是看如何设置response header的 content-type字段的。

org.springframework.http.converter.AbstractHttpMessageConverter#addDefaultHeaders
升级后的default charset为null,所以content-type 未设置charset。

image.png

看了一下 AbstractJackson2HttpMessageConverter,发现没有去除了init方法,所以没有设置默认的charset。

总结

Web services, Distributed Objects, etc
Most have same basic structure:
Read request
Decode request
Process service
Encode reply
Send reply

大多数Web服务 具有相同的基本结构,读取请求,解码请求,请求服务,编码回复,发送回复。

springmvc 请求都是使用HttpMessageConvert 编解码,找到对应的HttpMessageConvert,并深入其中的write和read方法,你就可以找到问题的源头。

参考资料

Scalable IO in Java

相关文章

网友评论

      本文标题:RestTemplate 调用中文乱码

      本文链接:https://www.haomeiwen.com/subject/uimnurtx.html