springmvc之编码问题(只看这⼀篇就⾜够了)分析基于springboot 2.2.1,其他版本源码可能略有区别
编码问题
在编程中我们经常遇到中⽂乱码问题,主要分为以下⼏种:
1. 返回⼀个页⾯
2. 返回⼀个string类型且⽅法注释了@ResponseBody注解
3. 返回⼀个json数据且⽅法注释了@ResponseBody注解
下⾯依次看看每种情况
1.1 返回页⾯乱码
这种情况在springboot中已经看不到了,因为springboot已经帮我们做了编码的⾃动配置为utf-8
⾸先看看正常页⾯编码是如何设置的:
在HttpEncodingAutoConfiguration这个⾃动配置类中,注⼊了CharacterEncodingFilter。
private final HttpProperties.Encoding properties;
public HttpEncodingAutoConfiguration(HttpProperties properties){
this.properties = Encoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter(){
CharacterEncodingFilter filter =new OrderedCharacterEncodingFilter();
filter.setEncoding(Charset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
设置这个filter的编码为配置⽂件中配置的字符集,默认情况下为UTF-8,我们也可以显式的指定
# 编码设置
spring:
http:
encoding:
force-request:true # 是否强制request都使⽤该种编码
force-response:true # 是否强制response都使⽤该种编码
charset: UTF-8
enabled:true
⽽在这个filter中则做了如下的设置:
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String encoding =getEncoding();
if(encoding != null){
if(isForceRequestEncoding()|| CharacterEncoding()== null){
request.setCharacterEncoding(encoding);
}
if(isForceResponseEncoding()){
response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
}
即设置了response的字符集。因此当返回正常页⾯的时候不需要我们去设置编码⽅式了
接下来看看错误页⾯的编码情况
前⾯分析过,默认的错误视图为StaticView,下⾯看看其中的render⽅法
public void render(Map<String,?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
if(response.isCommitted()){
String message =getMessage(model);
<(message);
return;
}
response.setContentType(TEXT_String());
//...
}
可以看到,这⾥会设置编码为UTF-8
1.2 返回⼀个string类型
1.2.1 原因
这种情况下会出现乱码:
@RequestMapping("/test9")
@ResponseBody
public String test9(){
return"成功";
}
由于返回的是注释了@ResponseBody注解的,因此会经过RequestResponseBodyMethodProcessor这个处理器处理返回值
if(selectedMediaType != null){
selectedMediaType = veQualityValue();
for(HttpMessageConverter<?> converter :ssageConverters){
GenericHttpMessageConverter genericConverter =(converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if(genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType):
converter.canWrite(valueType, selectedMediaType)){}
前⾯分析过,在处理返回值的时候,会依次从容器中的messageConverters选取⼀个能够使⽤的。⽽默认情况下的组件如下图所⽰
我们需要关注StringHttpMessageConverter和MapppingJackson2HttpMessageConverter
由于前者优先触发,因此当我们返回⼀个字符串时,会使⽤这个converter,在这个converter中,字符集被定义为
public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
因此,这⾥会出现乱码
1.2.2 解决⽅案
可以给RequestMappingHandlerAdapter重新设置messageConverter
在实现了WebMvcConfigurer接⼝的配置类中重写如下的⽅法:即可解决string类型返回值乱码
spring mvc和boot区别@Autowired
private StringHttpMessageConverter stringHttpMessageConverter;
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters){
converters.add(stringHttpMessageConverter);
}
注意事项:
1. 这⾥采⽤的springboot⾃动注⼊的StringHttpMessageConverter,这个采⽤的是utf-8编码
2. 这样设置后会覆盖容器中默认的converter,导致容器中只剩下这个,因此需要⼿动设置其他,但是这样做太⿇烦,需要复
制WebMvcConfigurationSupport中的addDefaultHttpMessageConverters。
下⾯给出第⼆种⽅案:
我们在前⾯⾃定义RedisSessionAttributeStore中(见springmvc之HandlerAdapter),重新注⼊了RequestMappingHandlerAdapter,因此这⾥我们可以⽤同样的⽅式,配置converters
@Autowired
private StringHttpMessageConverter stringHttpMessageConverter;
/**
* 使⽤RedisSessionAttributeStore代替默认的session存储
* @return
*/
@Override
protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter(){
RequestMappingHandlerAdapter adapter =new RequestMappingHandlerAdapter();
RedisSessionAttributeStore sessionAttributeStore =new RedisSessionAttributeStore(redisCacheClient);
adapter.setSessionAttributeStore(sessionAttributeStore);
//⾸先获取⽗类设置好的converters
List<HttpMessageConverter<?>> messageConverters =getMessageConverters();
//然后将其中的StringHttpMessageConverter进⾏替换
for(int i =0; i < messageConverters.size(); i++){
(i)instanceof StringHttpMessageConverter){
messageConverters.set(i, stringHttpMessageConverter);
}
}
return adapter;
}
通过这种思路,下⾯有第三种⽅案
同样是在MvcConfig中,还有下⾯这样的⽅法extendMessageConverters
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> messageConverters){
for(int i =0; i < messageConverters.size(); i++){
(i)instanceof StringHttpMessageConverter){
messageConverters.set(i, stringHttpMessageConverter);
}
}
}
这个⽅法与configureMessageConverters的区别就是,前者只是拓展,延伸converters,⽽后者是完全覆盖,相关逻辑可以见WebMvcConfigurationSupport中的getMessageConverters。
因此和第⼆种⽅案⼀样,我们重新设置StringHttoMessageConverter
1.3 返回⼀个对象
@RequestMapping("/test18")
@ResponseBody
public ResponseResult test18(){
return ResponseResult.ok("成功");
}
这种情况在springboot中也不会乱码,原因如下:
上⾯分析可知,这⾥⽤到了MapppingJackson2HttpMessageConverter
这个类在处理编码的时候见如下语句:
AbstractJackson2HttpMessageConverter的writeInternal⽅法
MediaType contentType = Headers().getContentType(); JsonEncoding encoding =getJsonEncoding(contentType);
protected JsonEncoding getJsonEncoding(@Nullable MediaType contentType){ if(contentType != null && Charset()!= null){
Charset charset = Charset();
for(JsonEncoding encoding : JsonEncoding.values()){
if(charset.name().JavaName())){
return encoding;
}
}
}
return JsonEncoding.UTF8;
}
可以看到,如果没有特别指定,则使⽤utf-8编码
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论