SpringMVC返回字符串中文乱码

一个例子
Spring版本为5.1.7

Controller中的方法如下:

@ResponseBody
@RequestMapping(value = "/call/{name}")
public String callSomeone(@PathVariable("name")String name) {
    return "call     "+name;
}

当这个方法被请求时会返回给浏览器一个字符串,现在遇到的问题是当name为中文时返回的字符串会乱码。
发现乱码的原因为response的Content-Type为text/html;charset=ISO_8859_1,charset应为UTF-8

设置了CharacterEncodingFilter之后还是有乱码,暂不清楚原因。

方式一,指定RequestMapping的produces属性:

@ResponseBody
@RequestMapping(value = "/call/{name}", produces = "text/html;charset=utf-8")
public String callSomeone(@PathVariable("name")String name) {
    return "call     "+name;
}

方式二,修改StringHttpMessageConverter的DefaultCharset属性(先说做法,在说分析过程):
做法:新建一个工具类 AppUtil实现ApplicationContextAware接口,并监听ContextRefreshedEvent事件

@Component
public class AppUtil implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent> {
    private ApplicationContext app;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.app = applicationContext;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        try {
            RequestMappingHandlerAdapter requestMappingHandlerAdapter = app.getBean(RequestMappingHandlerAdapter.class);
            if(requestMappingHandlerAdapter!=null) {
                List<HttpMessageConverter<?>> messageConverters = requestMappingHandlerAdapter.getMessageConverters();
                if(messageConverters!=null) {
                    // 获取bean容器中的StringHttpMessageConverter,并修改DefaultCharset属性
                    for(HttpMessageConverter item : messageConverters) {
                        if(item instanceof StringHttpMessageConverter) {
                            ((StringHttpMessageConverter) item).setDefaultCharset(StandardCharsets.UTF_8);
                        }
                    }
                }
            }
        }catch (BeansException e) {

        }
    }
}

-------------------到这里两种解决方式已经说完了,下面说一下第二种解决方式的思路--------------------
经查资料知道SpringMVC返回字符串的编码与StringHttpMessageConverter的DefaultCharset属性有关。
查看StringHttpMessageConverter的源码如下:

public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {

  /**
   * The default charset used by the converter.
   */
  public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;

  public StringHttpMessageConverter() {
    this(DEFAULT_CHARSET);
  }

  /**
   * A constructor accepting a default charset to use if the requested content
   * type does not specify one.
   */
  public StringHttpMessageConverter(Charset defaultCharset) {
    super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
  }
  ...
}

可以看出StringHttpMessageConverter的默认编码方式是ISO_8859_1,而编码实际存储在AbstractHttpMessageConverter
的defaultCharset属性中:

public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
  ...
  protected AbstractHttpMessageConverter(Charset defaultCharset, MediaType... supportedMediaTypes) {
    this.defaultCharset = defaultCharset;
    setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
  }

  @Nullable
  public Charset getDefaultCharset() {
    return this.defaultCharset;
  }
  ...

getDefaultCharset()方法上打一个断点,然后发起请求,查看方法的调用过程,
发现AbstractMessageConverterMethodProcessor类第275行对this.messageConverters进行遍历,
StringHttpMessageConverter的实例对象存放在AbstractMessageConverterMethodArgumentResolver的
messageConverters属性中。

那么StringHttpMessageConverter的实例对象是如何放到messageConverters属性中的呢?
找到RequestMappingHandlerAdapter(我也搞不清是怎么找到的了,可能是运气吧),RequestMappingHandlerAdapter部分源码如下:

// 初始化方法
public RequestMappingHandlerAdapter() {
    StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
    stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316

    this.messageConverters = new ArrayList<>(4);
    this.messageConverters.add(new ByteArrayHttpMessageConverter());

    // 看这里,这里的messageConverters还是RequestMappingHandlerAdapter自己的属性
    this.messageConverters.add(stringHttpMessageConverter);
    try {
      this.messageConverters.add(new SourceHttpMessageConverter<>());
    }
    catch (Error err) {
      // Ignore when no TransformerFactory implementation is available
    }
    this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}

// 实现了InitializingBean接口的afterPropertiesSet方法,该方法会在属性注入之后,初始化方法执行之前执行
@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
    initControllerAdviceCache();

    if (this.argumentResolvers == null) {
      // 看这里的getDefaultArgumentResolvers()方法
      List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
      this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    ...
}

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
    ...
    // 看这里,getMessageConverters()会获取RequestMappingHandlerAdapter的messageConverters
    resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
    ...
  }

public List<HttpMessageConverter<?>> getMessageConverters() {
    return this.messageConverters;
}

看一下RequestResponseBodyMethodProcessor的构造方法,注意一下继承关系

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
  public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
      @Nullable List<Object> requestResponseBodyAdvice) {
    // 调用父类的构造方法
    super(converters, null, requestResponseBodyAdvice);
  }
}

看一下AbstractMessageConverterMethodProcessor的这个构造方法

public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
    implements HandlerMethodReturnValueHandler {
  protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters,
      @Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) {
    // 继续调用父类的构造方法
    super(converters, requestResponseBodyAdvice);

    this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
    this.pathStrategy = initPathStrategy(this.contentNegotiationManager);
    this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
    this.safeExtensions.addAll(WHITELISTED_EXTENSIONS);
  }
}

再看AbstractMessageConverterMethodArgumentResolver的构造方法

  public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters,
      @Nullable List<Object> requestResponseBodyAdvice) {

    Assert.notEmpty(converters, "'messageConverters' must not be empty");

    // 到这里终于把RequestMappingHandlerAdapter里创建的StringHttpMessageConverter实例放到了
    // AbstractMessageConverterMethodArgumentResolver的messageConverters属性里面
    this.messageConverters = converters;
    this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
    this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
  }

现在又出现一个问题,RequestMappingHandlerAdapter是什么时候创建的呢?
可以查看RequestMappingHandlerAdapter的构造方法在哪里被调用了,
发现只有WebMvcConfigurationSupport的createRequestMappingHandlerAdapter调用,
相关代码如下:

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
  // @Bean注解说明这个方法返回的类会被放到bean容器中,beanName就是方法名
  @Bean
  public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
    ...
  }

  protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
    return new RequestMappingHandlerAdapter();
  }
  ...
}

DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport,
而DelegatingWebMvcConfiguration是Spring的一个配置类,所以会在Spring初始化容器时把该类和父类的
@Bean注解的方法创建的对象放入Spring的bean容器中

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
  ...
}

总结一下:
bean容器初始化 =》
调用WebMvcConfigurationSupport的requestMappingHandlerAdapter()方法=》
RequestMappingHandlerAdapter进行初始化(初始化过程中创建StringHttpMessageConverter对象,并放入自己的messageConverters属性中) =》
执行RequestMappingHandlerAdapter的afterPropertiesSet()方法,
会执行new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)
通过构造方法最终把StringHttpMessageConverter的对象放到了AbstractMessageConverterMethodArgumentResolver的messageConverters属性中

我所做的就是在bean容器初始化完成后,修改bean容器StringHttpMessageConverter对象的defaultCharset属性。
这样当SpringMVC返回字符串的时候就会取出StringHttpMessageConverter对象的defaultCharset属性的值作为
Content-Type的charset,解决乱码问题。

如果文中有错误之处或各位大佬有其他解决方法欢迎留言交流。

原文地址:https://www.cnblogs.com/binary-tree/p/11209307.html

时间: 2024-10-08 00:56:35

SpringMVC返回字符串中文乱码的相关文章

SpringMvc返回@ResponseBody中文乱码

使用SpringMvc的@ResponseBody返回指定数据的类型做为http体向外输出,在浏览器里返回的内容里有中文,会出现乱码,项目的编码.tomcat编码等都已设置成utf-8,如下返回的是一个字符串中文乱码. Java代码   @RequestMapping("user/get_comment_list.do") public @ResponseBody String getUserCommentList(Integer user_id,Byte type){ HashMap

解决springmvc返回json中文乱码

在pringmvc中通过设置@ResponseBody返回json乱码问题,这个问题上网找了很久,发现答案真是人云亦云,奉上我的解决方案: 解决方案一:需要导入 jackson-core-asl-1.9.11.jarjackson-mapper-asl-1.9.11.jar 包,其目的是设置了返回json的格式 <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

springmvc完成ajax功能以及返回字符串出现乱码的解决方法

1.加入jackson的jar包 Jackson是一个简单基于Java应用库,Jackson可以轻松的将Java对象转换成json对象和xml文档,同样也可以将json.xml转换成Java对象 返回字符串出现乱码的原因 原文地址:https://www.cnblogs.com/sh-0131/p/11456233.html

解决springmvc+mybatis+mysql中文乱码问题【转】

这篇文章主要介绍了解决java中springmvc+mybatis+mysql中文乱码问题的相关资料,需要的朋友可以参考下 近日使用ajax请求springmvc后台查询mysql数据库,页面显示中文出现乱码 最初在mybatis配置如下 <select id="queryContentById" resultType = "java.lang.String" parameterType="String" >     select t

SpringMVC 返回字符串

今天看到一段代码,关于SpringMVC的Controller中返回字符串的代码,这段代码被我称为2b代码(英文名:2b Code). @RequestMapping(value="twoB.do") public void twoBCode(HttpServletRequest request,HttpServletResponse response) { //.......... 此处省略 N行 try { response.setContentType("type=te

json_encode返回的中文乱码,解析不出

json_encode返回的中文乱码,解析不出,json_encode里面加个参数就行了 json_encode($data,JSON_UNESCAPED_UNICODE); 原文地址:https://www.cnblogs.com/yehuisir/p/12446009.html

SpringMVC 使用@ResponseBody返回json 中文乱码

这确实是个蛋疼的问题,Spring中解析字符串的转换器默认编码居然是ISO-8859-1 既然找到问题了,那就必须想办法改过来,不同版本的Spring好像方法还不一样,网上不少说的都是Spring3.*的,现在Spring4早都出来了 更改方式可以参考 http://stackoverflow.com/questions/3616359/who-sets-response-content-type-in-spring-mvc-responsebody http://www.cnblogs.com

SSM框架:解决后台传数据到前台中文乱码问题,使用@ResponseBody返回json 中文乱码

场景: 在实际运用场景中,当前台发起请求后,我们需要从后台返回数据给前台,这时,如果返回的数据中包含中文,则经常会出现在后台查询出来都是好好,但是传输回去就莫名的乱码了,而且,我们明明已经在 web.xml 中进行编码过滤了,但还是乱码,让人很头疼. 解决办法: 第一种:这种方法,估计很多人都知道,那就在 controller 中的每个方法的  @RequestMappering 注解中进行编码设置,如下所示: @RequestMapping(value = "/queryUserById&qu

微信开发模式无法验证以及返回消息中文乱码的情况

一开始我也纠结了这个问题很久,从微信公众平台上下载下来的例子不是utf-8格式的,但是却可以验证通过. 此时修改加入中文,返回消息会乱码,改成utf-8编码就显示正常了. 再来验证会不通过. 我一直纠结这个问题,其实没必要,只要第一次验证通过即可,后面修改成utf-8编码也不影响公众平台与服务号交互.