问题:
在前端通过get请求服务端返回String类型的服务时,会出现中文乱码问题
原因:
由于spring默认对String类型的返回的编码采用的是 StringHttpMessageConverter
>>> spring mvc的一个bug,spring MVC有一系列HttpMessageConverter去处理用@ResponseBody注解的返回值,如返回list则使用MappingJacksonHttpMessageConverter,返回string,则使用StringHttpMessageConverter,这个convert使用的是字符集是iso-8859-1,而且是final的:
public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
解决办法:
方案一:
对于需要返回字符串的方法添加注解,如下:只针对单个方法生效,不全局生效
@RequestMapping(value = "/getUsers", produces = "application/json; charset=utf-8")
public String getAllUser()throws JsonGenerationException, JsonMappingException, IOException{
List < User > users = userService.getAll();
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(users));
DataGrid dg = new DataGrid();
dg.setData(users);
return om.writeValueAsString(dg);
}
方案二:
在spring-servlet.xml中加入:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
方案三:
重写一个MessageConverter,然后注册到AnnotationMethodHandlerAdapter
package com.h5.common.converter; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.util.StreamUtils; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; public class EncodingAdapter extends AbstractHttpMessageConverter < String > { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private final Charset defaultCharset; private final List < Charset > availableCharsets; private boolean writeAcceptCharset; public EncodingAdapter() { this(DEFAULT_CHARSET); } public EncodingAdapter(Charset defaultCharset) { super(new MediaType[]{ new MediaType("text", "plain", defaultCharset), MediaType.ALL }); this.writeAcceptCharset = true; this.defaultCharset = defaultCharset; this.availableCharsets = new ArrayList(Charset.availableCharsets().values()); } public void setWriteAcceptCharset(boolean writeAcceptCharset) { this.writeAcceptCharset = writeAcceptCharset; } public boolean supports(Class < ? > clazz) { return String.class == clazz; } protected String readInternal(Class < ? extends String > clazz, HttpInputMessage inputMessage)throws IOException { Charset charset = this.getContentTypeCharset(inputMessage.getHeaders().getContentType()); return StreamUtils.copyToString(inputMessage.getBody(), charset); } protected Long getContentLength(String str, MediaType contentType) { Charset charset = this.getContentTypeCharset(contentType); try { return Long.valueOf((long)str.getBytes(charset.name()).length); } catch (UnsupportedEncodingException var5) { throw new IllegalStateException(var5); } } protected void writeInternal(String str, HttpOutputMessage outputMessage)throws IOException { if (this.writeAcceptCharset) { outputMessage.getHeaders().setAcceptCharset(this.getAcceptedCharsets()); } Charset charset = this.getContentTypeCharset(outputMessage.getHeaders().getContentType()); StreamUtils.copy(str, charset, outputMessage.getBody()); } protected List < Charset > getAcceptedCharsets() { return this.availableCharsets; } private Charset getContentTypeCharset(MediaType contentType) { return contentType != null && contentType.getCharSet() != null ? contentType.getCharSet() : this.defaultCharset; } }
//注册方法一:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <util:list> <bean class="com.ctrip.hotel.h5.common.converter.EncodingAdapter "> <constructor-arg index="0" value="UTF-8"/> </bean> </util:list> </property> </bean>
//注册方法二:
在webconfig.java中:
@Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { EncodingAdapter stringConverter = new EncodingAdapter(); converters.add(0, stringConverter); }
方案四:
直接新建一个如下的类,放入代码即可。
package com.h5.common.encode; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.stereotype.Component; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; /** * Created by xingyuzhu on 2017/2/27. * 解决@ResponseBody返回的响应中中文乱码问题. */ @Component public class EncodingPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{ if (bean instanceof RequestMappingHandlerAdapter) { List < HttpMessageConverter < ? >> convs = ((RequestMappingHandlerAdapter)bean).getMessageConverters(); for (HttpMessageConverter < ? > conv : convs) { if (conv instanceof StringHttpMessageConverter) { ((StringHttpMessageConverter)conv).setSupportedMediaTypes( Arrays.asList(new MediaType("text", "html", Charset.forName("UTF-8")))); } } } if (bean instanceof RequestResponseBodyMethodProcessor) { List < HttpMessageConverter < ? >> convs = ((RequestMappingHandlerAdapter)bean).getMessageConverters(); for (HttpMessageConverter < ? > conv : convs) { if (conv instanceof StringHttpMessageConverter) { ((StringHttpMessageConverter)conv).setSupportedMediaTypes( Arrays.asList(new MediaType("text", "html", Charset.forName("UTF-8")))); } } } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{ return bean; } }
方案五:
在我们的webconfig.java中,注册一个bean:该方法有缺陷RequestMappingHandlerAdapter中的其他messageconverter丢失,导致其他问题,比如返回的是一个jsp页面,就会挂掉
@Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter reqMapHAdapter = new RequestMappingHandlerAdapter(); ArrayList < HttpMessageConverter < ? >> msgConvs = new ArrayList < > (); StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(Charset.forName("UTF-8")); stringConverter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_PLAIN)); msgConvs.add(stringConverter); reqMapHAdapter.setMessageConverters(msgConvs); return reqMapHAdapter; }
方案六:
在webconfig.java中:
@Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(Charset.forName("UTF-8")); converters.add(0, stringConverter); }
方案七:(篡改框架的编码,推荐使用)
在webconfig.java中,篡改一下StringHttpMessageConverter的编码方式
@Override public void extendMessageConverters(List < HttpMessageConverter < ? >> converters) { HttpMessageConverter converter = Iterables.find(converters, new Predicate < HttpMessageConverter < ? >> () { @ Override public boolean apply( @ Nullable HttpMessageConverter < ? > input) { return input != null && input instanceof StringHttpMessageConverter; } }, null); if (converter == null) { StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(); stringConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "html", Charset.forName(UTF8)))); converters.add(1, stringConverter); //默认的StringHttpMessageConverter在第二个位置 return; } StringHttpMessageConverter stringHttpMessageConverter = (StringHttpMessageConverter)converter; stringHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "html", Charset.forName(UTF8)))); }