@ExceptionHandler全局异常捕获响应体返回中文乱码

@ExceptionHandler全局异常捕获响应体返回中文乱码,第1张

@ExceptionHandler全局异常捕获响应体返回中文乱码 问题现象

在全局异常处理后,发现响应体返回中文的错误信息竟然乱码了,如下:

问题原因
  • Controller
@RequestMapping(value = "/user/{user_id}", method = RequestMethod.DELETE, consumes =
                MediaType.APPLICATION_JSON_VALUE, produces = 
                MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
@ResponseStatus(code = HttpStatus.OK)
public String deleteUser(HttpServletRequest request, HttpServletResponse response,
                         @PathVariable(value = "user_id") String userId) {
    String userName = userService.getUserName(userId);
    if (userName == null) {
    	// 此处异常被全局异常处理
        throw new NullPointerException();
    }
    // delete user
    return "success";
}
  • 全局异常处理器ControllerAdvice
@Component
@ControllerAdvice
public class GlobalExceptionHandler {

    @Resource
    private MessageProcessor messageProcessor;
    
    
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public String handleException(HttpServletRequest request, Exception exception) {
        return messageProcessor.getMessage(request, INTERNAL_ERROR_DEFAULT_MESSAGE);
    }
}
  • 国际化资源包
response.500.message.0001=系统错误,请稍后重试。

response.500.message.0001=System error, please try again later.
问题分析
  1. 分析下原因,是因为触发了全局异常捕获,handleException()方法上标注了@ResponseBody,即返回的message会被放入响应体中返回给前端。和Controller方法不同的是,使用@RequestMapping方法指定了consumes=MediaType.APPLICATION_JSON_VALUE,即响应体会被json处理,但是@ExceptionHandler方法未指定响应体格式Content-Type,可以在postman中查看:

    由SpringMVC处理流程可知

6、返回ModelAndView之后仍然是交由HandleAdapter去处理,所以重点分析下Adapter。这里的Adapter实现类为RequestMappingHandlerAdapter,入口为handleInternal方法,调用invokeHandlerMethod()

  • handleInternal()
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
                                      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    ModelAndView mav;
    mav = invokeHandlerMethod(request, response, handlerMethod);
    return mav;
}
  • invokeHandlerMethod()
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                                           HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	// 创建ServletWebRequest对象
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        // 设置方法参数解析器
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        // 设置方法返回值处理器
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
		// ModelAndView容器,将上述参数设置进去并初始化相关配置
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
        // 异步请求处理AsyncWebRequest,不涉及已忽略
        // 调用此方法并处理返回值
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        webRequest.requestCompleted();
    }
}

  • invokeAndHandle()
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer 			mavContainer, Object... providedArgs) throws Exception {
    // 调用方法获取方法返回值,如果发生异常,此时获取的是异常处理的方法的返回值
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // 根据@ResponseStatus注解设置响应码
    setResponseStatus(webRequest);
    // 返回值为null时处理
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    else if (StringUtils.hasText(getResponseStatusReason())) {
        // 如果在@ResponseStatus注解设置reason(),则进去此处
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    // returnValueHandlers处理返回值
    try {
        this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}
  • handleReturnValue()
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
	// 在returnHandler列表中根据supportsReturnType()方法,获取第一个支持的Handler
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    // Handler中有messageConverters列表,根据messageConverter的canWrite()方法选择合适的messageConvert,并将message写入到response中。
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

由上分析可知,最终是根据messageConvert将返回值写入到response中。由于返回值是String,而messageConvert所以会使用StringHttpMessageConvert:

而StringHttpMessageConvert的Content-Type默认是text/plain;charset=UTF-8,编码方式是ISO-8859-1,所以产生中文乱码

解决方法
  • 解决方法一:在配置文件中指定StringHttpMessageConverter的字符集,推荐此方案。

    
    
        
        
            
                
                    
                        text/plain;charset=utf-8
                        text/html;charset=UTF-8
                    
                
            
        
    
    
  • 解决方法二:在bean后处理器中指定StringHttpMessageConverter的字符集,所有的被spring托管的bean都会执行postProcessAfterInitialization方法,建议使用解决方法一。

    @Component
    public class DefineCharSet implements BeanPostProcessor {
        //实例化之前调用
        @Nullable
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
        //实例化之后调用
        @Nullable
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            // 指定StringHttpMessageConverter的字符集
            if(bean instanceof StringHttpMessageConverter){
                MediaType mediaType = new MediaType("text", "html", Charset.forName("UTF-8"));
                List types = new ArrayList();
                types.add(mediaType);
                ((StringHttpMessageConverter) bean).setSupportedMediaTypes(types);
            }
            return bean;
        }
    }
    

测试:

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5692215.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存