在全局异常处理后,发现响应体返回中文的错误信息竟然乱码了,如下:
- 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")); Listtypes = new ArrayList (); types.add(mediaType); ((StringHttpMessageConverter) bean).setSupportedMediaTypes(types); } return bean; } }
测试:



