Spring RestTemplate 調用天氣預報接口可能遇到中文亂碼的問題,解決思路如下。
問題出現
我們在網上找了一個免費的天氣預報接口 http://wthrcdn.etouch.cn/weather_mini?citykey=101280601。我們希望調用該接口,並將返回的數據解析爲 JSON 格式。
核心業務邏輯如下:
private WeatherResponse doGetWeatherData(String uri) {
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
String strBody = null;
if (response.getStatusCodeValue() == 200) {
strBody = response.getBody();
}
ObjectMapper mapper = new ObjectMapper();
WeatherResponse weather = null;
try {
weather = mapper.readValue(strBody, WeatherResponse.class);
} catch (IOException e) {
e.printStackTrace();
}
return weather;
}
在瀏覽器裏面訪問該接口都挺正常。如下圖所示:
但在純 Spring 應用裏面,嘗試使用 RestTemplate 來調用,結果解析數據爲 JSON 失敗,因爲數據有亂碼。如下圖所示:
嘗試進行編碼轉換
一開始,我們認爲這可能是對方轉過來的數據不是 UTF-8 導致的,所以,嘗試加入了消息轉換器。
@Configuration
public class RestConfiguration {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().set(1,
new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 支持中文編碼
return restTemplate;
}
}
StringHttpMessageConverter 默認是 ISO_8859_1,所以我們設置爲了 UTF_8。
再次執行,發現仍然是亂碼。
找到問題的根源
這一次我沒有再瞎猜了,而是仔細觀察了 HTTP 的請求協議。發現消息頭裏面的蛛絲馬跡:
原來,數據是經過 GZIP 壓縮過的。默認情況下, RestTemplate 使用的是 JDK 的 HTTP 調用器,並不支持 GZIP 解壓,難怪解析不了。
解決方案
既然找到了問題所在,解決起來就簡單了。主要考慮了以下幾種方案。
1. 編寫 GIZP 工具類
處理 Gizp 壓縮的數據的工具類如下:
/**
* Welcome to https://waylau.com
*/
package com.waylau.spring.mvc.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
/**
* String Util.
*
* @since 1.0.0 2018年3月27日
* @author <a href="https://waylau.com">Way Lau</a>
*/
public class StringUtil {
/**
* 處理 Gizp 壓縮的數據.
*
* @param str
* @return
* @throws IOException
*/
public static String conventFromGzip(String str) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in;
GZIPInputStream gunzip = null;
in = new ByteArrayInputStream(str.getBytes("ISO-8859-1"));
gunzip = new GZIPInputStream(in);
byte[] buffer = new byte[256];
int n;
while ((n = gunzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
return out.toString();
}
}
核心業務邏輯如下:
private WeatherResponse doGetWeatherData(String uri) {
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
String strBody = null;
if (response.getStatusCodeValue() == 200) {
try {
strBody = StringUtil.conventFromGzip(response.getBody());
} catch (IOException e) {
e.printStackTrace();
}
}
ObjectMapper mapper = new ObjectMapper();
WeatherResponse weather = null;
try {
weather = mapper.readValue(strBody, WeatherResponse.class);
} catch (IOException e) {
e.printStackTrace();
}
return weather;
}
2. 使用 Apache HttpClient
使用 Apache HttpClient 作爲 REST 客戶端。Apache HttpClient 內置了對於 GZIP 的支持
@Configuration
public class RestConfiguration {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate(
new HttpComponentsClientHttpRequestFactory()); // 使用HttpClient,支持GZIP
restTemplate.getMessageConverters().set(1,
new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 支持中文編碼
return restTemplate;
}
}
核心業務邏輯如下:
private WeatherResponse doGetWeatherData(String uri) {
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
String strBody = null;
if (response.getStatusCodeValue() == 200) {
strBody = response.getBody();
}
ObjectMapper mapper = new ObjectMapper();
WeatherResponse weather = null;
try {
weather = mapper.readValue(strBody, WeatherResponse.class);
} catch (IOException e) {
e.printStackTrace();
}
return weather;
}
當然,使用該方案,需要引入 Apache HttpClient 的依賴。
最終效果,完美!
在 Spring Boot 中所使用的差異
也有學員問到,爲啥我在“基於Spring Cloud的微服務實戰”課程中,沒有同樣也是使用 RestTemplate, 調用同樣的接口,爲啥沒有出現亂碼的問題?
其實,細心的學員應該發現,在課程中,我們同樣也是使用了 Apache HttpClient,由於 Spring Cloud 本身也是基於 Spring Boot 來構建的,所以屏蔽了很多消息轉換的細節而言。
以下是 Spring Boot 中通過 RestTemplateBuilder 來構建 RestTemplate 的方式:
@Configuration
public class RestConfiguration {
@Autowired
private RestTemplateBuilder builder;
@Bean
public RestTemplate restTemplate() {
return builder.build();
}
}
所以學習編碼,知其然要知其所以然!
源碼
- 本文示例源碼,見 “Spring 5 案例大全”(https://github.com/waylau/spring-5-book) 的 “基於 RestTemplate 的天氣預報服務”例子
參考引用:
- 《Spring Boot 教程》:https://github.com/waylau/spring-boot-tutorial
- 《基於Spring Boot的博客系統實戰》:http://coding.imooc.com/class/125.html
- 原文同步至https://waylau.com/spring-resttemplate-gzip/