【项目地址】 从零开始学习springmvc 如果觉得有用可以关注一下公众号:码字不易,求赞求关注
五、Spring国际化和全局异常处理
5.1 国际化介绍
国际化的意思指对于同一个信息,可以识别不同的用户,从而展现出匹配用户当地语言信息。比如中文"提交",对于不懂中文的英国人你要使用“post”来表达一样。
对于页面来说,可以根据用户输入的Accept-Language 请求头来识别用户语言环境,从而加载已经提前准备好的语言资源包,展现出适配用户语言的环境。
5.1.1 ResourceBundle的介绍
ResourceBundle 是java.util 包下的工具类,主要用来解决国际化问题。当程序需要一个特定于语言环境的资源时(如 String),程序可以从适合当前用户语言环境的资源包(大多数情况下也就是.properties文件)中加载它。这样可以很大程度上独立于用户语言环境的程序代码,它将资源包中大部分(即便不是全部)特定于语言环境的信息隔离开来。
5.1.2 ResourceBundle的使用
-
新建语言资源包,为了适配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.
-
使用ResourceBundle package org.numb.common.util;
import java.util.Locale;
import java.util.ResourceBundle;
import org.junit.Test;
public class MessagesTest {
@Test
public 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"));
}
}
-
测试
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 。
<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 使用
public final String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException
这里封装一个工具MessageProcessor ,通过request的getLocale()方法获取Locale,ErrorCode 封装所有的国际化key。
public class ErrorCode {
public static final String INVALID_PARAMETERS_MESSAGE = "response.400.message.0001";
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);
@Resource
private 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);
}
return "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");
}
return "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信息。
public class AcceptHeaderLocaleResolver implements LocaleResolver {
@Override
public 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);
}
@Override
public 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 {
@Override
public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
return new TimeZoneAwareLocaleContext() {
@Override
public Locale getLocale() {
Locale locale = (Locale) WebUtils.getSessionAttribute(request, localeAttributeName);
if (locale == null) {
locale = determineDefaultLocale(request);
}
return locale;
}
@Override
public 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 {
@Override
public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
parseLocaleCookieIfNecessary(request);
return new TimeZoneAwareLocaleContext() {
@Override
@Nullable
public Locale getLocale() {
return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
}
@Override
@Nullable
public TimeZone getTimeZone() {
return (TimeZone)
request.getAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME);
}
};
}
@Override
public 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 {
@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);
}
}
测试
@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 "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()
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
mav = invokeHandlerMethod(request, response, handlerMethod);
return mav;
}
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
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);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
invocableMethod.invokeAndHandle(webRequest, mavContainer);
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
由上分析可知,最终是根据messageConvert将返回值写入到response中。由于返回值是String,而messageConvert所以会使用StringHttpMessageConvert:
而StringHttpMessageConvert的Content-Type 默认是text/plain;charset=UTF-8 ,编码方式是ISO-8859-1,所以产生中文乱码
-
解决方法一:在配置文件中指定StringHttpMessageConverter的字符集,推荐此方案。
<mvc:annotation-driven validator="validator">
<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 {
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
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
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<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}之间。
# 英文
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 {
@Resource
private MessageProcessor messageProcessor;
@ExceptionHandler(value = {BindException.class, MethodArgumentNotValidException.class})
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleBindException(HttpServletRequest request, BindException exception) {
String message = "";
String fieldName = "";
if (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);
}
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleConstraintViolationException(HttpServletRequest request, ConstraintViolationException exception) {
return exception.getMessage();
}
@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);
}
}
测试
|