SpringMVC返回字符串中文乱码
Posted binary-tree
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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,解决乱码问题。
如果文中有错误之处或各位大佬有其他解决方法欢迎留言交流。
以上是关于SpringMVC返回字符串中文乱码的主要内容,如果未能解决你的问题,请参考以下文章
SpringMVC 使用@ResponseBody返回json 中文乱码
SpringMVC 使用@ResponseBody返回json 中文乱码