1.问题描述
直接通过浏览器访问请求没有问题,但是通过 RestTemplate 访问请求却会出现乱码问题。
2.问题分析
首先我认为是 SpringBoot 版本、JDK 版本、项目结构配置的问题,但是即使我进行更新也没有效果。
然后再想的是默认注入的 RestTemplate 是不是内置的字符编码有问题。
于是我 Debug 查看 RestTemplate 的配置流程。
而该请求响应的 content-type
为 application/json;charset=utf-8
因此,我猜测是字符编码的问题,于是我需要重新设置一下默认的字符编码。
/**
* 通过 RestTemplate,我们可以发出 http 请求(支持 Restful 风格),
* 去调用 Controller 提供的 API 接口, 就像我们使用浏览器发出 http 请求, 调用该 API 接口一样
*
* @author 狐狸半面添
* @create 2024-03-29 0:30
*/
@Configuration
public class RestTemplateConfig {
/**
* 配置注入 RestTemplate bean/对象
*
* @return 实例对象
*/
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// 对所有 RestTemplate 的消息转换器进行遍历
restTemplate.getMessageConverters().forEach(httpMessageConverter -> {
// 找到 httpMessageConverter,修改默认的字符编码
if (httpMessageConverter instanceof StringHttpMessageConverter) {
((StringHttpMessageConverter) httpMessageConverter).setDefaultCharset(java.nio.charset.StandardCharsets.UTF_8);
}
});
return restTemplate;
}
}
但遗憾的是,还是没有用。再结合我之前使用 RestTemplate 的经历,也没有去设置过 DefaultCharset
,因此我觉得应该本来就不需要设置,RestTemplate 在获取到响应数据后会解析到 content-type
中的 charset=utf-8
从而按照声明的 utf-8 字符集进行解码处理。
在最后呢,我结合着浏览器可以正常响应数据而 RestTemplate 不可以,出现了乱码,以及我观察到响应头的一个字段 Content-Encoding: gzip
。因此我猜测是不是请求 URL 对应的服务器已经对数据进行了 gzip 压缩而导致的 RestTemplate 显示乱码。至于浏览器为什么能正常解析,这是因为浏览器已经对响应数据做了处理才进行的显示,浏览器发现 Content-Encoding: gzip
就对数据进行了解压缩。
那 RestTemplate 如何处理经过 gzip 压缩后的响应数据呢?那我们就需要借助 Apache HttpClient 的功能来处理 Gzip 压缩的响应。
原理:当 RestTemplate 接收到经过 Gzip 压缩的响应时,底层的 HTTP 客户端库会自动解压缩这个响应,然后将解压缩后的内容传递给 RestTemplate 进行处理。
3.问题解决
3.1 Apache HttpClient 依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
3.2 RestTemplate 配置类
import org.apache.http.impl.client.HttpClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* 通过 RestTemplate,我们可以发出 http 请求(支持 Restful 风格),
* 去调用 Controller 提供的 API 接口, 就像我们使用浏览器发出 http 请求, 调用该 API 接口一样
*
* @author 狐狸半面添
* @create 2023-02-17 1:43
*/
@Configuration
public class RestTemplateConfig {
/**
* 配置注入 RestTemplate bean/对象
*
* @return 实例对象
*/
@Bean
public RestTemplate restTemplate() {
// 加入 Apache HttpClient 的功能
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(HttpClients.createDefault());
RestTemplate restTemplate = new RestTemplate(factory);
// 这里也可以不注释
// restTemplate.getMessageConverters().forEach(httpMessageConverter -> {
// if (httpMessageConverter instanceof StringHttpMessageConverter) {
// ((StringHttpMessageConverter) httpMessageConverter).setDefaultCharset(java.nio.charset.StandardCharsets.UTF_8);
// }
// });
return restTemplate;
}
}
3.3 测试
@RestController
public class WeatherController {
/**
* 这里你需要根据自己的业务进行配置
*/
private final static String apiKey = "**************************************";
/**
* 访问风和天气 url 的访问格式
* {} 表示占位符
*/
private final static String URL = "https://geoapi.qweather.com/v2/city/lookup?location={cityName}&key={apiKey}";
@Resource
private RestTemplate restTemplate;
/**
* 根据用户输入的城市查询城市天气
* 请求示例:http://localhost:8080/city/长沙
*
* @param cityName
* @return
*/
@GetMapping("/city/{cityName}")
public String getWeatherByCityName(@PathVariable("cityName") String cityName) {
// 方式一
// String url = URL + cityName + "&key=" + apiKey;
// ResponseEntity<String> responseEntity = restTemplate.getForEntity(URL, String.class);
// 方式二
// public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
// 相应数据格式是 application/json; charset=utf-8
// 如果你需要做额外数据处理,可以先将响应的 json 数据转为 bean 做进一步处理
// 例如:JSON.parseObject(responseEntity.getBody(), Class<T> objectClass)
ResponseEntity<String> responseEntity = restTemplate.getForEntity(URL, String.class, cityName, apiKey);
return responseEntity.getBody();
}
}