前言
本文介绍使用Spring Boot实现国际化信息(i18n)提示。
Spring Boot 官方文档中关于国际化的文档资料:boot-features-internationalization
默认国际化配置
SpringBoot提供了自动配置类org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
。
可以看到自动配置类中提供的可配置参数为spring.messages
,其中典型配置为:
#默认配置国际化文件路径
spring.messages.basename=messages
默认将读取resources
名为messages
的Resource Bundle文件。
如果要使用该自动化配置的国际化资源,注入org.springframework.context.MessageSource
实例即可。
自定义国际化配置
如果需要更灵活的国际化配置,可以自行指定对应的国际化资源配置,自行读取。
这里以一个动态响应错误信息的国际化示例为例。
创建ResultCodeMessages国际化文件
在resources
目录下创建i18n
文件夹,用于存放国际化文件。
在i18n
目录下创建名为ResultCodeMessages
的Resource Bundle文件。
ResultCodeMessages.properties
00=success
97=request param error
98=not found
99=fail. arg : {0} , {1}
ResultCodeMessages_en.properties
#同ResultCodeMessages.properties,这里默认Locale为ENGLISH
00=success
97=request param error
98=not found
99=fail. arg : {0} , {1}
ResultCodeMessages_zh_CN.properties
00=成功
97=请求入参校验出错
98=未找到
99=失败。错了,说点啥吧:{0},{1}
创建标准响应结果类和响应码枚举类
响应码枚举类
publicenum ResultCodeEnum{//成功SUCCESS("00"),//失败FAIL("99"),//未找到NOT_FOUND("98"),//请求入参校验出错REQUEST_PARAM_ERROR("97");privatefinal String code;public StringgetCode(){return code;}ResultCodeEnum(String code){this.code= code;}publicstatic ResultCodeEnumgetEnum(String code){for(ResultCodeEnum resultCodeEnum: ResultCodeEnum.values()){if(resultCodeEnum.getCode().equals(code)){return resultCodeEnum;}}return null;}}
标准响应结果类
@Data@JsonInclude(JsonInclude.Include.NON_NULL)publicclassApiResult<T>implementsSerializable{private String code;private String message;@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")private LocalDateTime timestamp;private T data;privateApiResult(String code, String message, T data){this.code= code;this.message= message;this.timestamp= LocalDateTime.now();this.data= data;}publicstatic<T> ApiResult<T>success(T data){returnnewApiResult<>(ResultCodeEnum.SUCCESS.getCode(), null, data);}publicstatic ApiResult<Void>fail(String code, String message){returnnewApiResult<>(code, message, null);}}
自定义国际化拦截器
Spring Boot中国际化拦截器默认为org.springframework.web.servlet.i18n.LocaleChangeInterceptor
,默认国际化参数为URL参数locale
。
这里自定义修改国际化拦截器,默认国际化参数为URL参数(可以修改为从请求头获取),这里不做实质上的修改
publicclassCustomizeLocaleChangeInterceptorextendsLocaleChangeInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws ServletException{//从URL参数中获取
String newLocale= request.getParameter(getParamName());//可修改成从请求头获取//String newLocale = request.getHeader(getParamName());if(newLocale!= null){if(checkHttpMethod(request.getMethod())){
LocaleResolver localeResolver= RequestContextUtils.getLocaleResolver(request);if(localeResolver== null){thrownewIllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?");}try{
localeResolver.setLocale(request, response,parseLocaleValue(newLocale));}catch(IllegalArgumentException ex){if(isIgnoreInvalidLocale()){if(logger.isDebugEnabled()){
logger.debug("Ignoring invalid locale value ["+ newLocale+"]: "+ ex.getMessage());}}else{throw ex;}}}}// Proceed in any case.returntrue;}privatebooleancheckHttpMethod(String currentMethod){
String[] configuredMethods=getHttpMethods();if(ObjectUtils.isEmpty(configuredMethods)){returntrue;}for(String configuredMethod: configuredMethods){if(configuredMethod.equalsIgnoreCase(currentMethod)){returntrue;}}returnfalse;}}
注册自定义国际化拦截器
@ConfigurationpublicclassWebConfigurationimplementsWebMvcConfigurer{@Resource(name="customizeLocaleChangeInterceptor")
LocaleChangeInterceptor localeChangeInterceptor;@OverridepublicvoidaddInterceptors(InterceptorRegistry registry){
registry.addInterceptor(localeChangeInterceptor);}}
国际化配置
i18n配置
@ConfigurationpublicclassLocaleConfig{/**
* 默认本地化解析器
*/@Beanpublic LocaleResolverlocaleResolver(){
SessionLocaleResolver localeResolver=newSessionLocaleResolver();//指定默认语言
localeResolver.setDefaultLocale(Locale.ENGLISH);return localeResolver;}/**
* 默认本地化拦截器
*/@Bean("customizeLocaleChangeInterceptor")public LocaleChangeInterceptorlocaleChangeInterceptor(){
CustomizeLocaleChangeInterceptor localeChangeInterceptor=newCustomizeLocaleChangeInterceptor();//自定义国际化参数
localeChangeInterceptor.setParamName("language");return localeChangeInterceptor;}// result code : 响应码信息国际化配置@Bean("resultCodeMessageResource")public MessageSourceresultCodeMessageResource(){
ResourceBundleMessageSource resourceBundleMessageSource=newResourceBundleMessageSource();//指定国际化的Resource Bundle地址
resourceBundleMessageSource.setBasename("i18n/ResultCodeMessages");//指定国际化的默认编码
resourceBundleMessageSource.setDefaultEncoding("UTF-8");return resourceBundleMessageSource;}@Bean("resultCodeLocaleMessage")public LocaleMessageresultCodeLocaleMessage(){returnnewLocaleMessage(resultCodeMessageResource());}}
国际化资源工具类
publicclassLocaleMessage{privatefinal MessageSource messageSource;publicLocaleMessage(MessageSource messageSource){this.messageSource= messageSource;}public StringgetMessage(String code){returngetMessage(code,newObject[]{});}public StringgetMessage(String code, String defaultMessage){returngetMessage(code,newObject[]{}, defaultMessage);}public StringgetMessage(String code, String defaultMessage, Locale locale){returngetMessage(code,newObject[]{}, defaultMessage, locale);}public StringgetMessage(String code, Locale locale){returngetMessage(code,newObject[]{},"", locale);}public StringgetMessage(String code, Object[] args){returngetMessage(code, args,"");}public StringgetMessage(String code, Object[] args, Locale locale){returngetMessage(code, args,"", locale);}public StringgetMessage(String code, Object[] args, String defaultMessage){//根据应用部署的服务器系统来决定国际化returngetMessage(code, args, defaultMessage, LocaleContextHolder.getLocale());}public StringgetMessage(String code, Object[] args, String defaultMessage, Locale locale){return messageSource.getMessage(code, args== null?newObject[]{}: args, defaultMessage, locale);}}
全局统一异常处理
业务异常类
publicclassBusinessExceptionextendsRuntimeException{privatefinal String resultCode;privatefinal Object[] args;public StringgetResultCode(){return resultCode;}public Object[]getArgs(){return args;}publicBusinessException(ResultCodeEnum resultCodeEnum, Object[] args){this.resultCode= resultCodeEnum.getCode();this.args= args;}publicBusinessException(String code, Object[] args){this.resultCode= code;this.args= args;}publicstaticvoidthrowMessage(String errorCode, Object[] args){thrownewBusinessException(errorCode, args);}publicstaticvoidthrowMessage(ResultCodeEnum resultCodeEnum, Object[] args){thrownewBusinessException(resultCodeEnum.getCode(), args);}}
统一异常处理
@Slf4j@RestControllerAdviceclassGlobalExceptionHandler{@Resource(name="resultCodeLocaleMessage")
LocaleMessage resultCodeLocaleMessage;@ExceptionHandler(BindException.class)public ApiResult<Void>handlerBindExceptionHandler(HttpServletRequest req,final BindException e){
log.error(req.getServletPath()+" Bind Exception", e);
String message= e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(";"));return ApiResult.fail(ResultCodeEnum.REQUEST_PARAM_ERROR.getCode(), message);}@ExceptionHandler(MethodArgumentNotValidException.class)public ApiResult<Void>handlerMethodArgumentNotValidException(HttpServletRequest req,final MethodArgumentNotValidException e){
log.error(req.getServletPath()+" MethodArgumentNotValid Exception", e);
String message= e.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(";"));return ApiResult.fail(ResultCodeEnum.REQUEST_PARAM_ERROR.getCode(), message);}@ExceptionHandler(BusinessException.class)public ApiResult<Void>handlerBusinessException(HttpServletRequest req,final BusinessException e){
log.error(req.getServletPath()+" Business Exception", e);return ApiResult.fail(e.getResultCode(), resultCodeLocaleMessage.getMessage(e.getResultCode(), e.getArgs(), RequestContextUtils.getLocale(req)));}}
请求示例
@GetMapping("/success")public ApiResult<String>success(){return ApiResult.success("success");}@GetMapping("/fail")publicvoidfail(@RequestParam(value="code", required=false, defaultValue="99") String code,@RequestParam(value="content", required=false, defaultValue="错了吧,说点啥吧") List<String> contentList){
BusinessException.throwMessage(code, contentList.toArray());}
http测试示例
### 测试成功响应
GET http://localhost:8080/success HTTP/1.1
### 测试中文,响应码99
GET http://localhost:8080/fail?language=zh_CN&code=99&content=我没错&content=是系统的错 HTTP/1.1
### 测试英文,响应码99
GET http://localhost:8080/fail?language=en&code=99&content=I'm not wrong&content=It's the system's fault HTTP/1.1
示例
项目Demo:internationalization
效果图: