问题描述:
在使用httpclient开发接口时,客户端获取服务端返回的数据中包含中文部分出现???不能正常显示。
这里说下我使用的中间件的版本:
httpclient4.3.3
问题分析:
当时的思路是看乱码出在服务端还是客户端,因为是自己测试接口 代码里面服务端就打印输出了下发现是正常的,所以开始的问题寻找的出发点就放在了客户端获取数据乱码
的点上。下面贴出客户端测试的代码
String url = "http://localhost:8080/****";
StringBuffer sb = new StringBuffer();
sb.append(****);
String reqXml=sb.toString();
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpPost httppost = new HttpPost(url);
HttpEntity resEntity;
HttpEntity reqEntity = null;
//这里为了编码格式统一 专门赋值为了utf-8类型
reqEntity = new StringEntity(reqXml,Consts.UTF_8);
//httppost.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); 这个是在网上找的解决办法 发现放上去仍然不行
httppost.setEntity(reqEntity);
CloseableHttpResponse response;
response = httpclient.execute(httppost);
resEntity = response.getEntity();
String res = EntityUtils.toString(resEntity,"UTF-8");
System.out.println(res);
上面的方法是网上介绍比较多的解决httpclient乱码的办法,我试了但是依然是乱码???
其实在检查的过程中我发现EntityUtils.toString(resEntity,"UTF-8");这个设置是没有起到作用的,这里摘录下部分源码
public static String toString(
final HttpEntity entity, final Charset defaultCharset) throws IOException, ParseException {
Args.notNull(entity, "Entity");
final InputStream instream = entity.getContent();
if (instream == null) {
return null;
}
try {
Args.check(entity.getContentLength() <= Integer.MAX_VALUE,
"HTTP entity too large to be buffered in memory");
int i = (int)entity.getContentLength();
if (i < 0) {
i = 4096;
}
Charset charset = null;
try {
final ContentType contentType = ContentType.get(entity);
if (contentType != null) {
charset = contentType.getCharset();
}
} catch (final UnsupportedCharsetException ex) {
throw new UnsupportedEncodingException(ex.getMessage());
}
if (charset == null) {
charset = defaultCharset;
}
if (charset == null) {
charset = HTTP.DEF_CONTENT_CHARSET;
}
也就是说当ContentType.get(entity);获取的编码如果不为空的话 你设置的编码也就不起作用了。所以我这里没起作用,我就直接修改了这段代码拿出来进行测试,发现改为utf-8还是不起作用。折腾了几天 实在想不出什么原因,后来就换了思路猜想是不是在服务端返回数据后 写入response的时候乱码的了。这里声明下我这边框架用的springmvc
用的注解@ResponseBody
到此问题的点也就清晰了,这个地方我的方法返回的是String类型,由于springmvc3.2之后@RequestMapping使用了RequestMappingHandlerAdapter来处理请求,对于@ResponseBody,当为string时,会调用默认构造方法里面add的StringHttpMessageConverter,需要注意的是,这个converter默认的编码是“ISO-8859-1” 这是导致中文乱码的原因。这里介绍几种解决方法(我再这里采用的是方法二)
方法一(转自http://josh-persistence.iteye.com/blog/2085015
)查看StringHttpMessageConverter的源码,发下StringHttpMessageConverter类中的默认编码是ISO-8859-1编码,改编码是西欧字符集编码,显然不会支持中文。
- public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
- private final Charset defaultCharset;
- private final List<Charset> availableCharsets;
而且从上面的代码中可以看出,与字符相关的3个变量都是final的,意味着我们不能通过set或者构造器的注入去动态的更改上面3个值。
进一步分析源码可以看出,StringHttpMessageConverter继承与AbstractHttpMessageConverter<String>,分析该抽象类得,相关的操作字符编码的方法可以重写,于是我们可以自定义一个类继承StringHttpMessageConverter,然后重写相关的方法,代码如下:
- /**
- *
- */
- package com.chuanliu.platform.activity.basic.converter;
- import java.io.IOException;
- import java.nio.charset.Charset;
- import java.util.Arrays;
- import java.util.List;
- import org.springframework.http.HttpOutputMessage;
- import org.springframework.http.MediaType;
- import org.springframework.http.converter.StringHttpMessageConverter;
- import org.springframework.util.StreamUtils;
- /**
- * 用于处理中文乱码问题: Spring bug -
- *
- * In StringHttpMessageConverter, the default char set is ISO-8859-1(西欧字符集)
- *
- * @author Josh Wang(Sheng)
- *
- * @email josh_wang23@hotmail.com
- */
- public class UTF8StringHttpMessageConverter extends StringHttpMessageConverter {
- private static final MediaType UTF8 = new MediaType("text", "plain",
- Charset.forName("UTF-8"));
- private boolean writeAcceptCharset = true;
- @Override
- protected void writeInternal(String s, HttpOutputMessage outputMessage)
- throws IOException {
- if (this.writeAcceptCharset)
- outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
- Charset charset = UTF8.getCharSet();
- StreamUtils.copy(s, charset, outputMessage.getBody());
- }
- @Override
- protected List<Charset> getAcceptedCharsets() {
- return Arrays.asList(UTF8.getCharSet());
- }
- @Override
- protected MediaType getDefaultContentType(String t) throws IOException {
- return UTF8;
- }
- public boolean isWriteAcceptCharset() {
- return writeAcceptCharset;
- }
- public void setWriteAcceptCharset(boolean writeAcceptCharset) {
- this.writeAcceptCharset = writeAcceptCharset;
- }
- }
定义好上面的类后,只需要将该类注册到Spring的annotaion 处理序列中即可,于是当@ResponseBody中返回的类型是String类型时,Spring将会调用上面自定义类中复写的方法,从而返回UTF-8的编码:
- <mvc:annotation-driven>
- <mvc:message-converters register-defaults="true">
- <bean class="com.chuanliu.platform.activity.basic.converter.UTF8StringHttpMessageConverter"/>
- </mvc:message-converters>
- </mvc:annotation-driven>
解决方法2:最简单的方法:
在@Responsebody标注的方法上加上:produces="application/json;charset=utf-8"
如:
- @RequestMapping(value="/circle/{cid}", produces="application/json;charset=utf-8")
- @ResponseBody
@RequestMapping(value = "/produces", produces = "application/json"):表示将功能处理方法将生产json格式的数据,此时根据请求头中的Accept进行匹配,如请求头“Accept:application/json”时即可匹配;
@RequestMapping(value = "/produces", produces = "application/xml"):表示将功能处理方法将生产xml格式的数据,此时根据请求头中的Accept进行匹配,如请求头“Accept:application/xml”时即可匹配。
此种方式相对使用@RequestMapping的“headers = "Accept=application/json"”更能表明你的目的
解决方法3:指定返回值为对象不要返回String
既然上面分析了其原因是上面的StringHttpMessageConverter类中使用了ISO-8859-1编码,那么如果业务逻辑允许,我们完全可以不要返回一个字符串(String),完全可以返回一个对象,这样Spring默认会去调用一个叫MappingJackson2HttpMessageConverter的类,该类不仅会将你返回的对象转换成JSON返回,而且该类中使用的是我们想要的UTF-8字符集,