Spring MVC 十一:中文乱码

SpringMVC的中文乱码问题其实已经不是什么问题了,无非就是配置编码方式->解决问题。

但是由于SpringMVC可以通过:xml方式配置、Servlet3.0方式配置,以及是否使用@EnableWebMvc等,不同配置方式下,解决中文乱码问题的方案有所不同。

xml配置的方式

xml配置方式的解决方案最简单:

复制代码
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceRequestEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>forceResponseEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

在web.xml文件中加编码过滤器,并强制过滤器对Request和Response都生效,可以解决request请求、以及response返回参数中的中文乱码问题。

但是返回体,也就是response body中的中文乱码问题,以上过滤器方案无法解决。

Response body中的中文乱码问题需要在spring MVC中增加以下配置:

复制代码
   <mvc:annotation-driven >
        <!--设置响应输出字符集-->
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

前面一篇文章[Spring MVC 五:DispatcherServlet初始化之 mvc:annotation-driven] 分析过<mvc:annotation-driven />标签的解析过程,该标签在创建RequestMappingHandlerAdapter的过程中,会读取到xml文件中messageConverters的定义并设置到RequestMappingHandlerAdapter对象的messageConverters属性中并最终在DispatcherServlet处理请求的过程中生效。

Servlet3.0配置

Servlet3.0的配置方式,是指通过WebApplicationInitializer接口完成SpringMVC配置的方式。

可以通过接口方法getServletFilters增加编码过滤器:

复制代码
public class MvcInitializer
        extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
//        return null;
        return new Class[] {RootConfiguration.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
//        return null;
        return new Class[] {MvcConfiguration.class,CommonConfiguration.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }

    @Override
    protected Filter[] getServletFilters() {
//        ShallowEtagHeaderFilter shallowEtagHeaderFilter = new ShallowEtagHeaderFilter();
//        shallowEtagHeaderFilter.setWriteWeakETag(true);
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("utf-8");
        characterEncodingFilter.setForceEncoding(true);

        return new Filter[]{characterEncodingFilter};
    }
}

以上方式增加过滤器后,可以解决request和response请求及返回参数中的中文编码问题。

但是无法解决response body的中文乱码问题。

由于WebApplicationInitializer接口并没有提供任何关于messageConverters的接口,看了很多遍源码也并没有找到可以配置的地方,网上也没有找到相关解决方案...所以解决这个问题还是费了很多周折。

由于我们已经知道,response body的中文乱码问题最终是通过RequestMappingHandlerAdapter对象的messageConverters解决的,所以还是想通过定制化RequestMappingHandlerAdapter的初始化过程、设置其messageConverters的方式解决问题。

如果没有定制化处理的话,DispatcherServlet在初始化的过程中是在initStrategies方法中创建DispatcherServlet.properties文件中默认的RequestMappingHandlerAdapter,是通过反射机制直接new出来的,最终会调用到RequestMappingHandlerAdapter的默认构造器:

复制代码
public RequestMappingHandlerAdapter() {
		this.messageConverters = new ArrayList<>(4);
		this.messageConverters.add(new ByteArrayHttpMessageConverter());
		this.messageConverters.add(new StringHttpMessageConverter());
		try {
			this.messageConverters.add(new SourceHttpMessageConverter<>());
		}
		catch (Error err) {
			// Ignore when no TransformerFactory implementation is available
		}
		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
	}

默认构造器会直接new一个StringHttpMessageConverter()加进来,不修改的话StringHttpMessageConverter的默认字符集是ISO_8859_1,一定会出现中文乱码问题:

复制代码
	public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;

所以我们必须找到某种方式可以定制化RequestMappingHandlerAdapter的初始化过程。

继续分析源码:

复制代码
private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;

		if (this.detectAllHandlerAdapters) {
			// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerAdapter> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerAdapters = new ArrayList<>(matchingBeans.values());
				// We keep HandlerAdapters in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerAdapters);
			}
		}
		else {
			try {
				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
				this.handlerAdapters = Collections.singletonList(ha);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerAdapter later.
			}
		}

		// Ensure we have at least some HandlerAdapters, by registering
		// default HandlerAdapters if no other adapters are found.
		if (this.handlerAdapters == null) {
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}

发现initHandlerAdapters方法首先会从Spring容器中获取HandlerAdapter!

我们是否有办法定制一个HandlerAdapter、加入到Spring容器中?Spring当然给我们提供了这种机会,回想一下@Configuration+@Bean注解,是否就可以解决?

在MvcConfig文件中增加如下代码:

复制代码
@Configuration
@ComponentScan({"org.example.controller"})
public class MvcConfiguration  {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/pages/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

    //定制RequestMappingHandlerAdapter
    @Bean
    public RequestMappingHandlerAdapter handlerAdapter(){
        RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();

        List<HttpMessageConverter<?>> messageConverters;
        messageConverters = new ArrayList<>(4);
        messageConverters.add(new ByteArrayHttpMessageConverter());
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8"));

        messageConverters.add(stringHttpMessageConverter);
        try {
            messageConverters.add(new SourceHttpMessageConverter<>());
        }
        catch (Error err) {
            // Ignore when no TransformerFactory implementation is available
        }
        messageConverters.add(new AllEncompassingFormHttpMessageConverter());
        handlerAdapter.setMessageConverters(messageConverters);

        return  handlerAdapter;
    }

参考RequestMappingHandlerAdapter默认构造器的代码,修改其中StringHttpMessageConverter的创建过程、设置其默认字符集为UTF-8...验证后发现,问题已解决!

使用@EnableWebMvc

由于@EnableWebMvc是必须和@configuration配合使用的,所以,一定会存在配置类。这种情况下,配置类实现WebMvcConfigurer、通过扩展extendMessageConverters方法解决:

复制代码
@Configuration
@EnableWebMvc
@ComponentScan({"org.example.controller"})
public class MvcConfiguration implements WebMvcConfigurer{
    public MvcConfiguration(){
        System.out.println("mvc configuration constructor...");
    }
//    通过@EnableWebMVC配置的时候起作用,
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        for(HttpMessageConverter httpMessageConverter:converters){
            if(StringHttpMessageConverter.class.isAssignableFrom(httpMessageConverter.getClass())){
                ((StringHttpMessageConverter)httpMessageConverter).setDefaultCharset(Charset.forName("UTF-8"));
            }
        }
    }

}

上一篇 Spring MVC 五:DispatcherServlet初始化之 mvc:annotation-driven

相关推荐
曾令胜3 小时前
excel导出使用arthas动态追踪方法调用耗时后性能优化的过程
spring·性能优化·excel
.格子衫.3 小时前
Spring Boot 原理篇
java·spring boot·后端
多云几多3 小时前
Yudao单体项目 springboot Admin安全验证开启
java·spring boot·spring·springbootadmin
摇滚侠5 小时前
Spring Boot 3零基础教程,Spring Intializer,笔记05
spring boot·笔记·spring
Jabes.yang5 小时前
Java求职面试实战:从Spring Boot到微服务架构的技术探讨
java·数据库·spring boot·微服务·面试·消息队列·互联网大厂
聪明的笨猪猪5 小时前
Java Redis “高可用 — 主从复制”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
兮动人5 小时前
Spring Bean耗时分析工具
java·后端·spring·bean耗时分析工具
MESSIR226 小时前
Spring IOC(控制反转)中常用注解
java·spring
摇滚侠6 小时前
Spring Boot 3零基础教程,Demo小结,笔记04
java·spring boot·笔记
笨手笨脚の7 小时前
设计模式-迭代器模式
java·设计模式·迭代器模式·行为型设计模式