在全局异常处理后,发现响应体返回中文的错误信息竟然乱码了,如下:
- 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.问题分析
- 分析下原因,是因为触发了全局异常捕获,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; } }
测试:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)