首页 > 代码库 > Spring MVC @ResponseBody响应中文乱码

Spring MVC @ResponseBody响应中文乱码

问题:
在前端通过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 = "http://www.mamicode.com/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 = "http://www.mamicode.com/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="http://www.mamicode.com/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))));
}

 

Spring MVC @ResponseBody响应中文乱码