【项目地址】 从零开始学习springmvc
如果觉得有用可以关注一下公众号:码字不易,求赞求关注

五、Spring国际化和全局异常处理

  • 五、Spring国际化和全局异常处理
    • 5.1 国际化介绍
      • 5.1.1 ResourceBundle的介绍
      • 5.1.2 ResourceBundle的使用
      • 5.1.3 Spring国际化概述
        • 5.1.3.1 Spring国际化资源文件约定
        • 5.1.3.2 如何使用spring国际化
        • 5.1.3.3 如何确定本地信息(获取Locale信息)
          • 1. 前端如何传Locale信息
          • 2. 后端如何获取Locale信息
    • 5.2 全局异常处理
      • 5.2.1 @ControllerAdvice注解
      • 5.2.2 @ExceptionHandler注解
      • 5.2.3 全局异常处理使用
      • 5.2.4 响应体返回中文乱码
    • 5.3 Spring国际化整合Hibernate参数校验
      • 5.3.1 LocalValidatorFactoryBean配置validationMessageSource
      • 5.3.2 三种常见参数校验异常
        • 5.3.2.1 BindException
        • 5.3.2.2 MethodArgumentNotValidException
        • 5.3.2.3 ConstraintViolationException
      • 5.3.3 国际化错误信息
      • 5.3.4 全局异常处理

五、Spring国际化和全局异常处理

5.1 国际化介绍

国际化的意思指对于同一个信息,可以识别不同的用户,从而展现出匹配用户当地语言信息。比如中文"提交",对于不懂中文的英国人你要使用“post”来表达一样。

对于页面来说,可以根据用户输入的Accept-Language请求头来识别用户语言环境,从而加载已经提前准备好的语言资源包,展现出适配用户语言的环境。

5.1.1 ResourceBundle的介绍

ResourceBundlejava.util包下的工具类,主要用来解决国际化问题。当程序需要一个特定于语言环境的资源时(如 String),程序可以从适合当前用户语言环境的资源包(大多数情况下也就是.properties文件)中加载它。这样可以很大程度上独立于用户语言环境的程序代码,它将资源包中大部分(即便不是全部)特定于语言环境的信息隔离开来。

5.1.2 ResourceBundle的使用

  1. 新建语言资源包,为了适配spring国际化资源包的要求如下:

    • 新建资源包目录名为i18n目录
    • 以messages开头命名资源文件,则此目录下以messages开头的properties文件会被认为是一个资源包,资源包的baseName为messages。格式为messages_语言(小写)_国家(大写),如messages_zh_CN.properties代表简体中文,messages_en_US.properties代表美式英语。
    • messages.properties----存放不需要国际化的消息
    # messages_zh_CN.properties
    response.404.code=404
    response.404.message.0001=请求参数不合法。
    # messages_en_US.properties
    response.404.code=404
    response.404.message.0001=Request parameters are invalid.
    

  2. 使用ResourceBundle

    package org.numb.common.util;import java.util.Locale;
    import java.util.ResourceBundle;
    import org.junit.Test;public class MessagesTest {@Testpublic void test_messagesConsistency() {ResourceBundle zhMessages = ResourceBundle.getBundle("i18n/messages",                     Locale.SIMPLIFIED_CHINESE);System.out.println(zhMessages.getString("response.404.message.0001"));ResourceBundle enMessages = ResourceBundle.getBundle("i18n/messages",                     Locale.US);System.out.println(enMessages.getString("response.404.message.0001"));}
    }
    
  3. 测试

5.1.3 Spring国际化概述

5.1.3.1 Spring国际化资源文件约定

一般需要两个条件才可以确定一个特定类型的本地化信息,它们分别是“语言类型”和“国家/地区的类型”。比如中国大陆地区的中文是zh_CN,美式英语是en_US,英式英语是en_GB,国际标准规范ISO-3166规定了常用的国家和地区编码,这里给出了一些常用的国家地区编码

语言 简称
简体中文(中国) zh_CN
繁体中文(中国台湾) zh_TW
繁体中文(中国香港) zh_HK
英语(中国香港) en_HK
英语(美国) en_US
英语(英国) en_GB
英语(全球) en_WW
英语(加拿大) en_CA
英语(澳大利亚) en_AU
英语(爱尔兰) en_IE
英语(芬兰) en_FI
芬兰语(芬兰) fi_FI
英语(丹麦) en_DK
丹麦语(丹麦) da_DK
英语(以色列) en_IL
希伯来语(以色列) he_IL
英语(南非) en_ZA
英语(印度) en_IN
英语(挪威) en_NO
英语(新加坡) en_SG
英语(新西兰) en_NZ
英语(印度尼西亚) en_ID
英语(菲律宾) en_PH
英语(泰国) en_TH
英语(马来西亚) en_MY
英语(阿拉伯) en_XA
韩文(韩国) ko_KR
日语(日本) ja_JP
荷兰语(荷兰) nl_NL
荷兰语(比利时) nl_BE
葡萄牙语(葡萄牙) pt_PT
葡萄牙语(巴西) pt_BR
法语(法国) fr_FR
法语(卢森堡) fr_LU
法语(瑞士) fr_CH
法语(比利时) fr_BE
法语(加拿大) fr_CA
西班牙语(拉丁美洲) es_LA
西班牙语(西班牙) es_ES
西班牙语(阿根廷) es_AR
西班牙语(美国) es_US
西班牙语(墨西哥) es_MX
西班牙语(哥伦比亚) es_CO
西班牙语(波多黎各) es_PR
德语(德国) de_DE
德语(奥地利) de_AT
德语(瑞士) de_CH
俄语(俄罗斯) ru_RU
意大利语(意大利) it_IT
希腊语(希腊) el_GR
挪威语(挪威) no_NO
匈牙利语(匈牙利) hu_HU
土耳其语(土耳其) tr_TR
捷克语(捷克共和国) cs_CZ
斯洛文尼亚语 sl_SL
波兰语(波兰) pl_PL
瑞典语(瑞典) sv_SE
西班牙语(智利) es_CL

i18n(来源于internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称,一般将资源文件放在resources\i18n目录下,各资源文件以messages_开头

resources|----i18n|      |----messages.properties        // 不需要国际化的消息|       |----messages_zh_CN.properties  //  中文消息|       |----messages_en_US.properties  //  英文消息|       |----.....                      //  其他消息

5.1.3.2 如何使用spring国际化

Spring国际化主要是依赖资源文件绑定器ResourceBundleMessageSource

<!--spring国际化-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><property name="basename" value="i18n/messages"/><property name="defaultEncoding" value="UTF-8"/>
</bean>

basename类似于【5.1.2 ResourceBundle的使用】中ResourceBundle的basename,要配到资源名下,这里i18n是文件夹,messages是资源包名。defaultEncoding指定编码方式

在使用之前为了方便引入日志框架slf4j+log4j2,可以参考我的log4j2专栏。

配置如下

<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="log-demo-config" status="error" monitorInterval="10"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="[%d{yyyy-MM-dd;HH:mm:ss.SSS Z}] [%-5p] [%t] [%c] %m%n"/></Console></Appenders><Loggers><Logger name="org.numb" level="INFO" additivity="false"><AppenderRef ref="Console"/></Logger></Loggers>
</Configuration>

ResourceBundleMessageSource使用

/*** 抽象父类AbstractMessageSource的方法** @param code 指国际化资源文件中的key* @param args 可以将参数放入国际化信息中,如 "{0}", "{1,date}", "{2,time}" 或 null,后面具体演示如何使用* @param locale 本地化信息* @throws NoSuchMessageException 如果找不到国际化key指会抛此异常* @return 国际化后的信息*/
public final String getMessage(String code, @Nullable Object[] args, Locale locale)        throws NoSuchMessageException

这里封装一个工具MessageProcessor,通过request的getLocale()方法获取Locale,ErrorCode封装所有的国际化key。

public class ErrorCode {/*** 400相关错误国际化信息*/public static final String INVALID_PARAMETERS_MESSAGE = "response.400.message.0001";/*** 500相关错误国际化信息*/public static final String INTERNAL_ERROR_DEFAULT_MESSAGE =                    "response.500.message.0001";
}
@Component
public class MessageProcessor {private static final Logger LOGGER = LoggerFactory.getLogger(MessageProcessor.class);@Resourceprivate ResourceBundleMessageSource messageSource;public String getMessage(HttpServletRequest request, String msgCode, Object... params) {if (request == null || msgCode == null) {throw new IllegalArgumentException("request or message code is null!");}try {return messageSource.getMessage(msgCode, params, request.getLocale());} catch (NoSuchMessageException exception) {LOGGER.error("can not find the message of the message code of {}", msgCode);return messageSource.getMessage(INTERNAL_ERROR_DEFAULT_MESSAGE, params, request.getLocale());}}
}

资源文件

response.400.message.0001=请求参数不合法
response.404.message.0001=资源不存在。
response.500.message.0001=系统错误,请稍后重试。response.400.message.0001=Request parameters are invalid
response.404.message.0001=Request resource can not be found.
response.500.message.0001=System error, please try again later.

测试,UserController使用如下:

@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) {return messageProcessor.getMessage(request, INVALID_PARAMETERS_MESSAGE);}// delete userreturn "success";
}

ResourceBundleMessageSource也可以在国际化信息中传入参数,可以传入一个object[]数组,传入{0}, {1}…代表数组的每个索引的参数,如下所示

国际化资源包

response.400.message.0002=请求参数 {0} 不合法。response.400.message.0002=Request parameters {0} are invalid.

UserController

@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) {return messageProcessor.getMessage(request, "response.400.message.0002", "userId");}// delete userreturn "success";
}

测试:

5.1.3.3 如何确定本地信息(获取Locale信息)

上面直接根据request.getLocale()获取的Locale信息,此方法会寻找Accept-Language请求头信息,如果没有则返回默认的Locale。上小节我们并未特意强调传入Accept-Language请求头,所以是获取的默认Locale。这样一个问题是服务部署在不同环境上,默认Locale会不同导致国际化失效。下面将明确指出本地化的方法。

1. 前端如何传Locale信息
  • 基于浏览器语言(常用方式):根据Request Headers中的Accept-Language来判断。
  • 基于客户端传参:要求客户端第一次(或者每次)传递的自定义参数值来判断。如果在第一次传参中确定,那么locale信息要存入session或者cookie中,后面的请求语言方式则直接从两者中取,其有效时间与session和cookie设置的生命周期关联。这种方式一般用于需要覆盖请求头的Accept-Language。
  • 基于默认配置:当获取语言类型时没有找到对应类型时,会使用默认的语言类型
2. 后端如何获取Locale信息
  • 请求头获取AcceptHeaderLocaleResolver:默认根据Accept-Language请求头判断Locale信息。

    // 可以继承此类,重写resolveLocale方法,获取自定义的请求头
    public class AcceptHeaderLocaleResolver implements LocaleResolver {// 获取Locale信息@Overridepublic Locale resolveLocale(HttpServletRequest request) {Locale defaultLocale = getDefaultLocale();if (defaultLocale != null && request.getHeader("Accept-Language") == null) {return defaultLocale;}Locale requestLocale = request.getLocale();List<Locale> supportedLocales = getSupportedLocales();if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {return requestLocale;}Locale supportedLocale = findSupportedLocale(request, supportedLocales);if (supportedLocale != null) {return supportedLocale;}return (defaultLocale != null ? defaultLocale : requestLocale);}// 设置Locale信息,默认不支持设置Locale信息@Overridepublic void setLocale(HttpServletRequest request, @Nullable HttpServletResponse        response, @Nullable Locale locale) {throw new UnsupportedOperationException("Cannot change HTTP accept header - use a different locale resolution                     strategy");}}
    
  • session获取SessionLocaleResolver

    public class SessionLocaleResolver extends AbstractLocaleContextResolver {// 根据session获取Locale信息,获取不到返回默认Locale@Overridepublic LocaleContext resolveLocaleContext(final HttpServletRequest request) {return new TimeZoneAwareLocaleContext() {@Overridepublic Locale getLocale() {Locale locale = (Locale) WebUtils.getSessionAttribute(request,                            localeAttributeName);if (locale == null) {locale = determineDefaultLocale(request);}return locale;}@Overridepublic void setLocaleContext(HttpServletRequest request, @Nullable                         HttpServletResponse response,@Nullable LocaleContext localeContext) {Locale locale = null;TimeZone timeZone = null;if (localeContext != null) {locale = localeContext.getLocale();if (localeContext instanceof TimeZoneAwareLocaleContext) {timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();}}WebUtils.setSessionAttribute(request, this.localeAttributeName, locale);WebUtils.setSessionAttribute(request, this.timeZoneAttributeName, timeZone);}
    }
    
  • cookie获取CookieLocaleResolver

    public class CookieLocaleResolver extends CookieGenerator implements                                                    LocaleContextResolver {// 根据COOKIE的.LOCALE获取Locale信息@Overridepublic LocaleContext resolveLocaleContext(final HttpServletRequest request) {parseLocaleCookieIfNecessary(request);return new TimeZoneAwareLocaleContext() {@Override@Nullablepublic Locale getLocale() {return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);}@Override@Nullablepublic TimeZone getTimeZone() {return (TimeZone)                                              request.getAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME);}        };}@Overridepublic void setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response,@Nullable LocaleContext localeContext) {Assert.notNull(response, "HttpServletResponse is required for" +      "CookieLocaleResolver");Locale locale = null;TimeZone timeZone = null;if (localeContext != null) {locale = localeContext.getLocale();if (localeContext instanceof TimeZoneAwareLocaleContext) {timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();}addCookie(response,(locale != null ? toLocaleValue(locale) : "-") + (timeZone != null                             ? '/' + timeZone.getID() : ""));}else {removeCookie(response);}request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,(locale != null ? locale : determineDefaultLocale(request)));request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,(timeZone != null ? timeZone : determineDefaultTimeZone(request)));}
    }
    

一般正常使用传入Accept-Language请求头即可

5.2 全局异常处理

如果在Controller请求中触发了异常,未处理的话,默认会提示500,服务器内部错误。但是有些异常又不得不去处理,如果在业务代码抛异常处处理,那就会产生很多冗余代码。因为大多数异常触发的原因是相似的。幸好Spring也提供了通用机制去处理。

5.2.1 @ControllerAdvice注解

@ControllerAdvice注解的类可以在多个Controller类(@Controller注解标注的类)之间共享由@ExceptionHandler@InitBinder、或者@ModelAttribute注解的代码,且该类必须由@Component注解。简而言之,一个被Spring托管的bean(@Component),如果加了@ControllerAdvice注解,则其内部的方法如果被@ExceptionHandler@InitBinder、或者@ModelAttribute注解,则所有的Controller只要满足特定条件,都可以走到这个类中的方法。

5.2.2 @ExceptionHandler注解

@ExceptionHandler注解作用是Controller类中抛出异常,会在此处进行处理,可以用于类或者方法上。@ExceptionHandler注解的方法的参数可以是以下任意数量的参数:

  • 异常参数:@ExceptionHandler中的value()指定得异常类;
  • request:ServletRequest或者HttpServletRequest等;
  • response:ServletResponse或者HttpServletResponse等;
  • session:HttpSession等,如果加了此参数将强制存在相应的会话。因此,这个参数永远不会为空。但是,会话访问可能不是线程安全的,尤其是在 Servlet 环境中:如果允许多个请求同时访问一个会话,可以将"synceOnSession"标志切换为"true"。
  • WebRequest或者NativeWebRequest:允许通用请求参数访问以及请求/会话属性访问,而无需绑定到 Servlet API。
  • Locale:本地化信息
  • InputStream或者Reader: Servlet API 暴露的原始 InputStream/Reader。
  • OutputStream或者Writer:Servlet API 暴露的原始 OutputStream/Writer。
  • Model:模型作为从处理程序方法返回模型映射的替代方法。注意,提供的模型未使用常规模型属性预先填充,因此始终为空,以便为特定于异常的视图准备模型。

@ExceptionHandler注解的方法的返回可以是:

  • ModelAndView对象;
  • Model对象:其视图名称通过 org.springframework.web.servlet.RequestToViewNameTranslator 隐式确定
  • Map对象:暴露的视图,其视图名称通过 org.springframework.web.servlet.RequestToViewNameTranslator 隐式确定
  • View对象;
  • String:视图名称
  • @ResponseBody
  • HttpEntity<?> 或者 ResponseEntity<?> 对象:对象(仅限 Servlet)来设置响应标头和内容。响应实体正文将使用消息转换器进行转换并写入响应流
  • void:参数中加response,可以手动将响应设置进response。

5.2.3 全局异常处理使用

@Component
@ControllerAdvice
public class GlobalExceptionHandler {@Resourceprivate MessageProcessor messageProcessor;/*** 处理其他未被捕获的异常,返回服务器内部错误,响应500** @param request   http请求* @param exception 参数绑定异常* @return 异常响应国际化信息*/@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);}
}

测试

@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();// return messageProcessor.getMessage(request, "response.400.message.0002", // "userId");}// delete userreturn "success";
}

发现竟然乱码了,下面分析下原因

5.2.4 响应体返回中文乱码

分析下原因,是因为触发了全局异常捕获,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()方法,获取第一个支持的HandlerHandlerMethodReturnValueHandler 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的字符集,推荐此方案。

    <!--开启SpringMVC注解驱动-->
    <!--指定validator-->
    <mvc:annotation-driven validator="validator"><!--指定StringHttpMessageConverter的字符集--><mvc:message-converters><bean class="org.springframework.http.converter.StringHttpMessageConverter"><property name="supportedMediaTypes"><list><value>text/plain;charset=utf-8</value><value>text/html;charset=UTF-8</value></list></property></bean></mvc:message-converters>
    </mvc:annotation-driven>
    
  • 解决方法二:在bean后处理器中指定StringHttpMessageConverter的字符集,所有的被spring托管的bean都会执行postProcessAfterInitialization方法,建议使用解决方法一。

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

测试:

5.3 Spring国际化整合Hibernate参数校验

5.3.1 LocalValidatorFactoryBean配置validationMessageSource

<!--Hibernate Validator参数校验-->
<!--开启Spring的校验功能,使用Hibernate Validator校验-->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"><!--参数校验配置为Hibernate Validator--><property name="providerClass" value="org.hibernate.validator.HibernateValidator"/><!--参数校验错误信息国际化--><property name="validationMessageSource" ref="messageSource"/>
</bean>

5.3.2 三种常见参数校验异常

5.3.2.1 BindException

BindException,仅对于表单提交的请求体body校验有效(@Validated 或@Valid注解),校验失败会抛出此类异常,对于以json格式提交将会失效。

5.3.2.2 MethodArgumentNotValidException

MethodArgumentNotValidException是BindException的子类,提交的请求体body为json格式时有效,校验失败是会抛出身份校验异常。

5.3.2.3 ConstraintViolationException

Spring的校验能力(@Validated加MethodValidationPostProcessor,参考4.4 Spring的校验机制以及@Validated注解的使用)会抛出此种异常。对请求参数(@RequestParam)和路径参数(@PathVariable)有效。

5.3.3 国际化错误信息

前面章节4.3.2 hibernate-validator常用注解中参数校验注解message信息都由一个默认值,如@NotNull注解(validation-api包的注解)所示:

如果在校验注解上重新指定message,则可以把默认的message这些值国际化,

  • messages_zh_CN.properties
response.400.message.0001=请求参数不合法。
response.400.message.0002=请求参数 {0} 不合法。
response.404.message.0001=资源不存在。
response.500.message.0001=系统错误,请稍后重试。# 参数校验默认国际化key值
javax.validation.constraints.NotNull.message=请求参数 %s 不能为null。
javax.validation.constraints.NotBlank.message=请求参数 %s 不能为空。
javax.validation.constraints.Min.message=请求参数 %s 不能小于{value}。
javax.validation.constraints.Max.message=请求参数 %s 不能大于{value}。
javax.validation.constraints.Size.message=请求参数 %s 长度(或数量)必须在{min}和{max}之间。
javax.validation.constraints.Pattern.message=请求参数 %s 不满足正则规则{regexp}。
org.hibernate.validator.constraints.Length.message=请求参数 %s 长度必须在{min}和{max}之间。
org.hibernate.validator.constraints.Range.message=请求参数 %s 必须在{min}和{max}之间。
  • message_en_US.properties
# 英文
response.400.message.0001=Request parameters are invalid.
response.400.message.0002=Request parameters {0} are invalid.
response.404.message.0001=Request resource can not be found.
response.500.message.0001=System error, please try again later.# 参数校验默认国际化key值
javax.validation.constraints.NotNull.message=The request parameter %s cannot be null.
javax.validation.constraints.NotBlank.message=The request parameter %s cannot be blank.
javax.validation.constraints.Min.message=Request parameter %s cannot be less than {value}.
javax.validation.constraints.Max.message=Request parameter %s cannot be greater than {value}.
javax.validation.constraints.Size.message=The request parameter %s length (or quantity) must be between {min} and {max}.
javax.validation.constraints.Pattern.message=Request parameter %s does not satisfy regular rule {regexp}.
org.hibernate.validator.constraints.Length.message=The request parameter %s length must be between {min} and {max}.
org.hibernate.validator.constraints.Range.message=The request parameter %s must be between {min} and {max}

这里{value}{min}{max}在国际化的时候会将注解内的值替换,比如@Min和@Max的value()在国际化是会替换javax.validation.constraints.Min.message=请求参数 %s 不能小于{value}。中的{value}。这里也一并将参数的名称放入国际化消息中,可以使用%s,在返回错误信息之前使用String.format()方法将参数名放入国际化后的消息。

5.3.4 全局异常处理

package org.numb.common.handler;import static org.numb.common.i18n.ErrorCode.INTERNAL_ERROR_DEFAULT_MESSAGE;
import static org.numb.common.i18n.ErrorCode.INVALID_PARAMETERS_MESSAGE;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import org.apache.commons.lang3.StringUtils;
import org.numb.common.i18n.MessageProcessor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;@Component
@ControllerAdvice
public class GlobalExceptionHandler {@Resourceprivate MessageProcessor messageProcessor;/*** BindException和MethodArgumentNotValidException异常,响应码为400** @param request   http请求* @param exception 参数绑定异常* @return 异常响应国际化信息*/@ExceptionHandler(value = {BindException.class, MethodArgumentNotValidException.class})@ResponseBody@ResponseStatus(value = HttpStatus.BAD_REQUEST)public String handleBindException(HttpServletRequest request, BindException exception) {String message = "";String fieldName = "";// 从exception中获取参数名称fieldNameif (exception.getBindingResult().getFieldError() != null) {String field = exception.getBindingResult().getFieldError().getField();if (StringUtils.isNotBlank(field)) {fieldName = field;}}if (exception.getBindingResult().getAllErrors().size() > 0) {message = exception.getBindingResult().getAllErrors().get(0).getDefaultMessage();}// 如果国际化消息为空,使用默认的国际化消息if (StringUtils.isBlank(message)) {message = messageProcessor.getMessage(request, INVALID_PARAMETERS_MESSAGE);}return String.format(message, fieldName);}/*** 处理ConstraintViolationException异常,响应400** @param request   http请求* @param exception 参数绑定异常* @return 异常响应国际化信息*/@ExceptionHandler(value = ConstraintViolationException.class)@ResponseBody@ResponseStatus(value = HttpStatus.BAD_REQUEST)public String handleConstraintViolationException(HttpServletRequest request, ConstraintViolationException exception) {return exception.getMessage();}/*** 处理其他未被捕获的异常,返回服务器内部错误,响应500** @param request   http请求* @param exception 参数绑定异常* @return 异常响应国际化信息*/@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);}
}

测试

从零开始学习springmvc(5)——Spring国际化和全局异常处理相关推荐

  1. Spring Boot入门——全局异常处理

    Spring Boot入门--全局异常处理 参考文章: (1)Spring Boot入门--全局异常处理 (2)https://www.cnblogs.com/studyDetail/p/702758 ...

  2. 【学习笔记】springboot中的全局异常处理 和@ControllerAdvice的使用

    文章目录 全局异常处理 例子 @ControllerAdvice的其他使用场景 全局异常处理 系统中异常包括:编译时异常和运行时异常RuntimeException ,前者通过捕获异常从而获取异常信息 ...

  3. springmvc高级(拦截器,全局异常处理,文件上传)

    SpringMVC 1.文件上传 文件上传: 指的就是将用户本地计算机中文件上传到服务器上的过程称之为文件上传 1).文件上传编程步骤 # 1.项目中引入相关依赖 <dependency> ...

  4. 案例01-tlias智能学习辅助系统04-登录认证+全局异常处理

    目录 1.基础登录 2.登录校验 2.1.会话跟踪技术: Cookie.Session JWT令牌 2.2.请求过滤 方式一.过滤器(Filter) 方法二:拦截器(interceptor) Filt ...

  5. Spring Boot WebFlux 全局异常处理(404,500)解决IllegalArgumentException: Property 'message...

    直接上代码,一共需要写3个类: 处理json异常消息的类: import org.reactivestreams.Publisher; import org.springframework.core. ...

  6. spring框架做全局异常捕获_springboot springmvc抛出全局异常的解决方法

    springboot中抛出异常,springboot自带的是springmvc框架,这个就不多说了. springmvc统一异常解决方法这里要说明的是.只是结合了springboot的使用而已.直接上 ...

  7. SpringMVC学习日记 1.Spring框架

    SpringMVC学习日记 1.Spring框架 Spring简介 Spring框架是一个开源框架,由Rod Johnson组织和开发,生产目的在于简化企业级应用的开发. 主要特性 非侵入(no-in ...

  8. springboot+jsp中文乱码_【spring 国际化】springMVC、springboot国际化处理详解

    在web开发中我们常常会遇到国际化语言处理问题,那么如何来做到国际化呢? 你能get的知识点? 使用springgmvc与thymeleaf进行国际化处理. 使用springgmvc与jsp进行国际化 ...

  9. Spring、SpringMVC、Spring Boot、Spring Cloud 概念、关系及区别

    注:此文章转载于其他大神 一.正面解读: Spring主要是基于IOC反转Beans管理Bean类,主要依存于SSH框架(Struts+Spring+Hibernate)这个MVC框架,所以定位很明确 ...

最新文章

  1. NLP(3)| seq to seq 模型
  2. JAVA个go哪个写web方便_Go语言实现的一个简单Web服务器
  3. 【2020任燕翔-考研专业院校选择指南】【计算机考研——针对教材、攻略】
  4. wxWidgets:wxApp概览
  5. linux 串口 vmin vtime ,Linux串口c_cc[VTIME]和c_cc[VMIN]属性设置的作用
  6. 微软亚研院:CV领域2019年重点论文推荐
  7. 安卓“新皇”来了!华为Mate 40确定10月22日发布
  8. 原码和补码在计算机中的应用,原码,补码和反码在计算机中的作用
  9. corelab mysql_ALinq 让Mysql变得如此简单_MySQL
  10. JSP内置 对象(下)
  11. winform 更新服务器程序
  12. android_驱动_qcom_【高通SDM660平台 Android 10.0】(10) --- Camera Sensor lib 与 Kernel Camera Probe 代码分析
  13. Linux系统下的JDK_11下载安装与环境配置
  14. RapidMiner介绍与实践(二)贝叶斯分类器
  15. 数据挖掘算法学习及应用场景
  16. 回声状态网络(ESN)的公式推导及代码实现
  17. 前端监控--vue项目中使用友盟统计监控
  18. Introdution(前言)
  19. pt-table-checksum使用总结
  20. 他人的建议和意见对自已做决定的影响

热门文章

  1. Xz1android9打电话延迟,索尼Xperia XZ与XZ1系列正式获得Android 9升级;但这新
  2. 一个Android应届生从上海离职,威力加强版
  3. MySQL实现连表查询
  4. c语言谷歌的招聘题目扣分,google的环环相扣招聘试题
  5. pdf文件怎么修改文字
  6. TAAL新任CEO Jerry Chan访谈:我们将如何从当前危机中引领新经济体制
  7. 算法竞赛——强连通分量
  8. 证明完全立方数模9同余_牡丹江2立方玻璃钢蓄水池报价
  9. 1048 数字加密 (20分)
  10. 3D可视化大屏是如何实现的?