Spring RestTemplate 调用天气预报接口乱码的解决

Spring RestTemplate 调用天气预报接口可能遇到中文乱码的问题,解决思路如下。

问题出现

我们在网上找了一个免费的天气预报接口 http://wthrcdn.etouch.cn/weather_mini?citykey=101280601。我们希望调用该接口,并将返回的数据解析为 JSON 格式。

核心业务逻辑如下:

1private WeatherResponse doGetWeatherData(String uri) { 2 3 ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class); 4 5 String strBody = null; 6 7 if (response.getStatusCodeValue() == 200) { 8 strBody = response.getBody(); 9 } 10 11 ObjectMapper mapper = new ObjectMapper(); 12 WeatherResponse weather = null; 13 14 try { 15 weather = mapper.readValue(strBody, WeatherResponse.class); 16 } catch (IOException e) { 17 e.printStackTrace(); 18 } 19 20 return weather; 21}

在浏览器里面访问该接口都挺正常。如下图所示:

在浏览器里面访问该接口都挺正常

但在纯 Spring 应用里面,尝试使用 RestTemplate 来调用,结果解析数据为 JSON 失败,因为数据有乱码。如下图所示:

RestTemplate 数据有乱码

尝试进行编码转换

一开始,我们认为这可能是对方转过来的数据不是 UTF-8 导致的,所以,尝试加入了消息转换器。

1@Configuration 2public class RestConfiguration { 3 4 @Bean 5 public RestTemplate restTemplate() { 6 RestTemplate restTemplate = new RestTemplate(); 7 restTemplate.getMessageConverters().set(1, 8 new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 支持中文编码 9 return restTemplate; 10 } 11 12}

StringHttpMessageConverter 默认是 ISO_8859_1,所以我们设置为了 UTF_8。

再次执行,发现仍然是乱码。

找到问题的根源

这一次我没有再瞎猜了,而是仔细观察了 HTTP 的请求协议。发现消息头里面的蛛丝马迹:

HTTP 的请求协议

原来,数据是经过 GZIP 压缩过的。默认情况下, RestTemplate 使用的是 JDK 的 HTTP 调用器,并不支持 GZIP 解压,难怪解析不了。

解决方案

既然找到了问题所在,解决起来就简单了。主要考虑了以下几种方案。

1. 编写 GIZP 工具类

处理 Gizp 压缩的数据的工具类如下:

1/** 2 * Welcome to https://waylau.com 3 */ 4package com.waylau.spring.mvc.util; 5 6import java.io.ByteArrayInputStream; 7import java.io.ByteArrayOutputStream; 8import java.io.IOException; 9import java.util.zip.GZIPInputStream; 10 11/** 12 * String Util. 13 * 14 * @since 1.0.0 2018年3月27日 15 * @author <a href="https://waylau.com">Way Lau</a> 16 */ 17public class StringUtil { 18 19 /** 20 * 处理 Gizp 压缩的数据. 21 * 22 * @param str 23 * @return 24 * @throws IOException 25 */ 26 public static String conventFromGzip(String str) throws IOException { 27 ByteArrayOutputStream out = new ByteArrayOutputStream(); 28 ByteArrayInputStream in; 29 GZIPInputStream gunzip = null; 30 31 in = new ByteArrayInputStream(str.getBytes("ISO-8859-1")); 32 gunzip = new GZIPInputStream(in); 33 byte[] buffer = new byte[256]; 34 int n; 35 while ((n = gunzip.read(buffer)) >= 0) { 36 out.write(buffer, 0, n); 37 } 38 39 return out.toString(); 40 } 41}

核心业务逻辑如下:

1private WeatherResponse doGetWeatherData(String uri) { 2 3 ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class); 4 String strBody = null; 5 6 if (response.getStatusCodeValue() == 200) { 7 try { 8 strBody = StringUtil.conventFromGzip(response.getBody()); 9 } catch (IOException e) { 10 e.printStackTrace(); 11 } 12 } 13 14 ObjectMapper mapper = new ObjectMapper(); 15 WeatherResponse weather = null; 16 17 try { 18 weather = mapper.readValue(strBody, WeatherResponse.class); 19 } catch (IOException e) { 20 e.printStackTrace(); 21 } 22 23 return weather; 24}

2. 使用 Apache HttpClient

使用 Apache HttpClient 作为 REST 客户端。Apache HttpClient 内置了对于 GZIP 的支持

1@Configuration 2public class RestConfiguration { 3 4 @Bean 5 public RestTemplate restTemplate() { 6 RestTemplate restTemplate = new RestTemplate( 7 new HttpComponentsClientHttpRequestFactory()); // 使用HttpClient,支持GZIP 8 restTemplate.getMessageConverters().set(1, 9 new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 支持中文编码 10 return restTemplate; 11 } 12 13}

核心业务逻辑如下:

1private WeatherResponse doGetWeatherData(String uri) { 2 3 ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class); 4 5 String strBody = null; 6 7 if (response.getStatusCodeValue() == 200) { 8 strBody = response.getBody(); 9 } 10 11 ObjectMapper mapper = new ObjectMapper(); 12 WeatherResponse weather = null; 13 14 try { 15 weather = mapper.readValue(strBody, WeatherResponse.class); 16 } catch (IOException e) { 17 e.printStackTrace(); 18 } 19 20 return weather; 21}

当然,使用该方案,需要引入 Apache HttpClient 的依赖。

最终效果,完美!

最终效果

在 Spring Boot 中所使用的差异

也有学员问到,为啥我在“基于Spring Cloud的微服务实战”课程中,没有同样也是使用 RestTemplate, 调用同样的接口,为啥没有出现乱码的问题?

其实,细心的学员应该发现,在课程中,我们同样也是使用了 Apache HttpClient,由于 Spring Cloud 本身也是基于 Spring Boot 来构建的,所以屏蔽了很多消息转换的细节而言。

以下是 Spring Boot 中通过 RestTemplateBuilder 来构建 RestTemplate 的方式:

1@Configuration 2public class RestConfiguration { 3 4 @Autowired 5 private RestTemplateBuilder builder; 6 7 @Bean 8 public RestTemplate restTemplate() { 9 return builder.build(); 10 } 11 12}

所以学习编码,知其然要知其所以然!

源码

参考引用:

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )