springmvc请求返回一个字符_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 {

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> 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 {

/**

* 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 implements HttpMessageConverter {

...

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 resolvers = getDefaultArgumentResolvers();

this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);

}

...

}

private List getDefaultArgumentResolvers() {

List resolvers = new ArrayList<>();

...

// 看这里,getMessageConverters()会获取RequestMappingHandlerAdapter的messageConverters

resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));

...

}

public List> getMessageConverters() {

return this.messageConverters;

}

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

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

public RequestResponseBodyMethodProcessor(List> converters,

@Nullable List requestResponseBodyAdvice) {

// 调用父类的构造方法

super(converters, null, requestResponseBodyAdvice);

}

}

看一下AbstractMessageConverterMethodProcessor的这个构造方法

public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver

implements HandlerMethodReturnValueHandler {

protected AbstractMessageConverterMethodProcessor(List> converters,

@Nullable ContentNegotiationManager manager, @Nullable List 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> converters,

@Nullable List 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,解决乱码问题。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值