• Spring MVC 十一:中文乱码


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

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

    xml配置的方式

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

    
        CharacterEncodingFilter
        org.springframework.web.filter.CharacterEncodingFilter
        
          encoding
          utf-8
        
        
          forceRequestEncoding
          true
        
        
          forceResponseEncoding
          true
        
      
      
        CharacterEncodingFilter
        /*
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

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

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

       
            
            
                
                    
                        
                            text/html;charset=utf-8
                        
                    
                
            
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    前面一篇文章[Spring MVC 五:DispatcherServlet初始化之 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};
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    以上方式增加过滤器后,可以解决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());
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    	public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
    
    • 1

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

    继续分析源码:

    private void initHandlerAdapters(ApplicationContext context) {
    		this.handlerAdapters = null;
    
    		if (this.detectAllHandlerAdapters) {
    			// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
    			Map 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");
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    发现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> 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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    参考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> converters) {
            for(HttpMessageConverter httpMessageConverter:converters){
                if(StringHttpMessageConverter.class.isAssignableFrom(httpMessageConverter.getClass())){
                    ((StringHttpMessageConverter)httpMessageConverter).setDefaultCharset(Charset.forName("UTF-8"));
                }
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

  • 相关阅读:
    Java 流处理之收集器
    9、Nacos服务注册服务端源码分析(总结篇)
    EasyExcel实现动态表头功能
    手写一个HashMap
    Java 异步编程 (5 种异步实现方式详解)
    宝塔面板操作日志是存放在哪里的? 如何删除部分日志记录?
    策略枚举:消除在项目里大批量使用if-else的正确姿势
    工业级路由器如何异地组网及其作用
    工作多年,技术认知不足,个人成长慢,职业发展迷茫,该怎么办?
    数据结构·顺序表
  • 原文地址:https://blog.csdn.net/weixin_44612246/article/details/133818453