项目中用到了百度的AI
接口,我们使用Spring
封装的RestTemplate
工具进行Http
请求,出现了乱码问题,百度API
文档写的是该接口响应的内容是UTF-8
编码。
如上图所示,百度AI
接口返回的信息并没有指定编码(其实是UTF-8
的编码格式,只是没指定)。所以Spring Boot
就以默认的ISO-8859-1
对返回的数据流进行了编码,转换为String
,这时其实已经乱码了,接着因为我的开发环境是UTF-8
的,所以打印出来全是乱码。转码过程大概是这样的:(UTF-8
—>字节数据—>ISO-8859-1
—>UTF-8
)
{
"log_id":7806616030292411514,
"items":[
{
"sentiment":2,
"abstract":"东西很好åƒ",
"prop":"东西",
"begin_pos":0,
"end_pos":12,
"adj":"很好"
},
{
"sentiment":2,
"abstract":"æœåŠ¡ä¹Ÿå¾ˆä¸é”™",
"prop":"æœåŠ¡",
"begin_pos":0,
"end_pos":18,
"adj":"好"
}
]
}
对返回信息进行编码转换。转换方法:对响应结果result
进行ISO-8859-1
解码和UTF-8
编码即可。转码过程大概是这样的:(UTF-8
—>字节数据—>ISO-8859-1
—>字节数据—>UTF-8
)
//工具类中注入静态成员属性。
private static RestTemplate restTemplate;
@Autowired
public void setRestTemplate(RestTemplate restTemplate) {
BaiduUtils.restTemplate = restTemplate;
}
//方法代码
String url = SERVER_COMMENT_TAG.replace("{TOKEN}", token);
HttpHeaders headers = new HttpHeaders();
MediaType mediaType = MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(mediaType);
JSONObject jsonObject = new JSONObject();
jsonObject.put("type", type);
jsonObject.put("text", comment);
HttpEntity<String> paramEntity = new HttpEntity<>(jsonObject.toJSONString(), headers);
//这里提交的参数是String类型的
String baiduResult = restTemplate.postForObject(url, paramEntity, String.class);
//这里进行编码转换
String result = new String(baiduResult.getBytes("ISO-8859-1"), "UTF-8");
log.info(result );
打印结果
{
"log_id":7806616030292411514,
"items":[
{
"sentiment":2,
"abstract":"东西很好吃",
"prop":"东西",
"begin_pos":0,
"end_pos":12,
"adj":"很好"
},
{
"sentiment":2,
"abstract":"服务也很不错",
"prop":"服务",
"begin_pos":0,
"end_pos":18,
"adj":"好"
}
]
}
RestTemplate
类的部分源码:
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
private static boolean romePresent;
private static final boolean jaxb2Present;
private static final boolean jackson2Present;
private static final boolean jackson2XmlPresent;
private static final boolean jackson2SmilePresent;
private static final boolean jackson2CborPresent;
private static final boolean gsonPresent;
private static final boolean jsonbPresent;
static {
ClassLoader classLoader = RestTemplate.class.getClassLoader();
romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}
private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
//。。。。。。省略代码
/**
* 使用默认设置创建 RestTemplate 的新实例。
* 默认初始化 HttpMessageConverter HttpMessageConverters。
*/
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
// 处理 String 类型的参数
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter(false));
try {
this.messageConverters.add(new SourceHttpMessageConverter<>());
}
catch (Error err) {
// 忽略没有TransformerFactory实现可用的情况
}
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
this.messageConverters.add(new AtomFeedHttpMessageConverter());
this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
}
else if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
// 处理 JSON 类型数据
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
}
else if (jsonbPresent) {
this.messageConverters.add(new JsonbHttpMessageConverter());
}
if (jackson2SmilePresent) {
this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter());
}
if (jackson2CborPresent) {
this.messageConverters.add(new MappingJackson2CborHttpMessageConverter());
}
this.uriTemplateHandler = initUriTemplateHandler();
}
//。。。。。。省略代码
}
通过跟踪断点,简单分析下RestTemplate内部源码,发现以 JavaBean,Map,JSONObject 格式分别提交数据时,RestTemplate
底层均采用了MappingJackson2HttpMessageConverter
来处理请求。而以String
格式提交数据时,底层其实采用的是StringHttpMessageConverter
来处理请求。
打开StringHttpMessageConverter
源码,发现该构造器默认的字符集是ISO-8859-1
:
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
/**
* 转换器使用的默认字符集。
*/
public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
@Nullable
private volatile List<Charset> availableCharsets;
private boolean writeAcceptCharset = true;
/**
* 使用"ISO-8859-1"作为默认字符集的默认构造函数。
* @see #StringHttpMessageConverter(Charset)
*/
public StringHttpMessageConverter() {
this(DEFAULT_CHARSET);
}
/**
* 如果所请求的内容类型没有指定,则接受要使用的默认字符集的构造函数。
*/
public StringHttpMessageConverter(Charset defaultCharset) {
super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
}
//。。。。。。省略代码
}
直接在List
中对StringHttpMessageConverter
设置编码,并进行覆盖。
public class RestUtil {
private final static RestTemplate restTemplate;
static {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
//做一些其他设置
requestFactory.setConnectTimeout(3000);
requestFactory.setReadTimeout(3000);
restTemplate = new RestTemplate(requestFactory);
//解决乱码问题,在列表中的对应位置直接覆盖
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
}
//工具方法
}
public class RestUtil {
private final static RestTemplate restTemplate;
static {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
//做一些其他设置
requestFactory.setConnectTimeout(3000);
requestFactory.setReadTimeout(3000);
restTemplate = new RestTemplate(requestFactory);
//解决乱码问题,遍历寻找,进行覆盖
List<HttpMessageConverter<?>> list = restTemplate.getMessageConverters();
for (HttpMessageConverter<?> httpMessageConverter : list) {
if(httpMessageConverter instanceof StringHttpMessageConverter) {
((StringHttpMessageConverter) httpMessageConverter).setDefaultCharset(StandardCharsets.UTF_8);
break;
}
}
}
//工具方法
}
RestTemplate
提供的构建器RestTemplateBuilder
也提供了设置MessageConverter
的方法,所以可以在构建时设置特殊的编码就可以解决问题了。
public class RestUtil {
private final static RestTemplate restTemplate;
static {
//解决乱码问题,遍历寻找,进行覆盖
StringHttpMessageConverter messageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
//添加额外的HttpMessageConverter,在构建器上配置的任何转换器都将替换RestTemplate的默认转换器。
restTemplate = new RestTemplateBuilder().additionalMessageConverters(messageConverter).build();
}
//工具方法
}
RestTemplate
底层是通过HttpURLConnection
实现的。
(1)getForObject
RestTemplate restTemplate = new RestTemplate(https://blog.csdn.net/mryang125/article/details/80955558);
String url = "http://localhost:8080/user/{id}";
UserVo userVo = restTemplate.getForObject(url, UserVo.class, id);
第一个参数表示URL
,第二个参数表示返回类型,第三个参数表示URI
中对应的参数,是一个可变长参数,可按顺序写多个参数。
(2)getForEntity
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/users/{userName}/{note}/{start}/{limit}";
//使用map封装多个参数
Map<String, Object> params = new HashMap<>();
params.put("userName", userName);
params.put("note", note);
params.put("start", start);
params.put("limit", limit);
ResponseEntity<List> responseEntity = restTemplate.getForEntity(url, List.class, params);
List<UserVo> userVos = responseEntity.getBody();
(3)postForObject
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/user";
//设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
//创建请求实体对象
HttpEntity<UserVo> request = new HttpEntity<>(newUserVo, headers);
User user = restTemplate.postForObject(url, request, User.class);
(4)postForEntity
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/user";
//设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
//创建请求实体对象
HttpEntity<UserVo> request = new HttpEntity<>(newUserVo, headers);
//请求服务器
ResponseEntity<User> responseEntity = restTemplate.postForEntity(url, request, User.class);
//获取响应体
User user = responseEntity.getBody();
//获取响应头
HttpHeaders respHeaders = responseEntity.getHeaders();
//获取响应属性
List<String> success = respHeaders.get("success");
//获取响应状态码
int statusCode = responseEntity.getStatusCodeValue();
(5)delete
RestTemplate restTemplate = new RestTemplate();
restTemplate.delete("http://localhost:8080/use/{id}", id);
(6)exchange
RestTemplate
还提供了一个exchange
方法,该方法比上面的方法灵活,可以通过制定参数实现各种Http
请求。下面列出Spring
提供的八种方法。
<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType) throws RestClientException;
<T> ResponseEntity<T> exchange(String url,HttpMethod method, HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException;
<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType) throws RestClientException;
<T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> responseType) throws RestClientException;
<T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, ParameterizedTypeReference<T> responseType) throws RestClientException;
下面写一个使用例子:
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/user";
//设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
//创建请求实体对象
HttpEntity<UserVo> request = new HttpEntity<>(newUserVo, headers);
//请求服务器
ResponseEntity<User> responseEntity = restTemplate.exchange(url, HttpMethod.POST, request, User.class);
//获取响应体
User user = responseEntity.getBody();
//获取响应头
HttpHeaders respHeaders = responseEntity.getHeaders();
//获取响应属性
List<String> success = respHeaders.get("success");
//获取响应状态码
int statusCode = responseEntity.getStatusCodeValue();
//获取资源
String url1 = "http://localhost:8080/user/{id}";
ResponseEntity<User> responseEntity1 = restTemplate.exchange(url1, HttpMethod.GET, null, User.class, id);
//获取响应体
User user1 = responseEntity1.getBody();