配置国际化资源目录
在application.yml里配置message的目录
spring:messages:encoding: UTF-8basename: i18n/msg

这里我配置在resource目录下的/i18n/msg,里面每个msg.properties就是不同语言对应的消息文字。
定义全局异常消息国际化
我们可以自定义一种抽象异常父类,需要给出国际化消息提示的时候,传入消息的key,然后自动返回国际化的提示。
如果你想要你的异常可以进行国际化的提示,继承这个国际化类就OK。
public abstract class LocalizationException extends RuntimeException {private static final long serialVersionUID = 7718379187319837197L;private static MessageSource messageSource;public LocalizationException(String messageKey, String... messageArguments) {this(getMessage(messageKey, messageArguments), null, messageKey, messageArguments);}public static MessageSource getMessageSource() {return messageSource;}public static void setMessageSource(MessageSource messageSource) {LocalizationException.messageSource = messageSource;}private final String messageKey;private final String[] messageArguments;public LocalizationException(String systemErrorMessage, Throwable cause, String messageKey,String... messageArguments) {super(systemErrorMessage, cause);this.messageKey = messageKey;this.messageArguments = messageArguments;}LocalizationException(String systemErrorMessage, String messageKey, String... messageArguments) {this(systemErrorMessage, null, messageKey, messageArguments);}LocalizationException(Throwable cause, String messageKey, String... messageArguments) {this(cause.getMessage(), cause, messageKey, messageArguments);}LocalizationException(Throwable cause) {super(cause);this.messageKey = null;this.messageArguments = null;}/*** @return the messageKey - the key to lookup up in the localized resource bundles, such as* 'user.not.found'.*/public String getMessageKey() {return messageKey;}/*** @return the messageArguments - - an array of arguments that will be filled in for params* within the message (params look like "{0}", "{1,date}", "{2,time}" within a message),* or <code>null</code> if none.*/public Object[] getMessageArguments() {return messageArguments;}@Overridepublic String getMessage() {if (StringUtils.isNotBlank(getMessageKey())) {return messageSource.getMessage(getMessageKey(), getMessageArguments(), getMessageKey(),LocaleContextHolder.getLocale());} else {return super.getMessage();}}private static String getMessage(String messageKey, String... messageArguments) {if (StringUtils.isNotBlank(messageKey)) {return messageSource.getMessage(messageKey, messageArguments, messageKey, LocaleContextHolder.getLocale());} else {return messageKey;}}}
这里本地化异常LocalizationException里包含了MessageSource国际化资源对象,最终通过把国际化消息的key和arguments传递给messageSource获得国际化的消息。
下面定义一个授权异常子类实现本地化异常父类:
public class AuthException extends LocalizationException {public AuthException(String messageKey, String... messageArguments) {super(messageKey, messageArguments);}}
之后我们把异常的Key全局统一管理起来,写到一个接口常量中。
public interface ExceptionCode {String ERROR_JWT_INVALID_SIGNATURE = "error.jwt.invalid.signature";String ERROR_JWT_INVALID_TOKEN = "error.jwt.invalid.token";}
配置全局异常拦截
上一步实现了全局异常的消息国际化,下一步配置全局国际化异常的拦截处理。
@Slf4j@RestControllerAdvicepublic class GlobalExceptionHandler {/*** 拦截国际化异常* @param e* @return*/@ExceptionHandler(value = {LocalizationException.class})public Result baseException(LocalizationException e) {log.error("LocalizationException:{}", e.getMessage());return Result.fail(e.getMessage());}/*** 拦截RuntimeException* @param ex* @return*/@ExceptionHandler(value = {RuntimeException.class})@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result runtimeException(RuntimeException ex) {log.error("exception:", ex);return Result.fail(ExceptionUtils.getMessage(ex));}/*** 拦截Exception* @param ex* @return*/@ExceptionHandler(value = {Exception.class})@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result exception(Exception ex) {log.error("exception:", ex);return Result.fail(ApiErrorCode.FAILED.getMsg());}/*** 在调用RPC、二方包、或动态生成类的相关方法* @param throwable* @return*/@ExceptionHandler(value = {Throwable.class})@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result throwable(Throwable throwable) {log.error("throwable:", throwable);return Result.fail(ApiErrorCode.FAILED.getMsg());}}
重点在于@RestControllerAdvice注解和@ExceptionHandler进行拦截异常。如果没有拦截到的异常可以使用Exception异常兜底。
配置 messageSource Bean
然后我们需要将messageSource配置Bean,并注入到LocalizationException的静态变量messageSource中使用。
@Configurationpublic class MessageConfig {/*** 静态注入messageSource到LocalizationException中*/@Bean@Autowiredpublic MethodInvokingFactoryBean methodInvokingFactoryBean(MessageSource messageSource) {MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();methodInvokingFactoryBean.setTargetClass(LocalizationException.class);methodInvokingFactoryBean.setTargetMethod("setMessageSource");methodInvokingFactoryBean.setArguments(messageSource);return methodInvokingFactoryBean;}/*** 自定义messageSource文件的位置,更灵活,此方法可不写,因为application.yml已经配置了*/@Beanpublic ReloadableResourceBundleMessageSource messageSource() {ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();messageSource.setBasename("classpath:/i18n/msg");// 设置缓存加载的属性文件的秒数,-1表示要永久缓存(与java.util.ResourceBundle的默认行为匹配)messageSource.setCacheSeconds(10);messageSource.setDefaultEncoding(StandardCharsets.UTF_8);return messageSource;}}
ReloadableResourceBundleMessageSource的继承结构:
使用国际化异常消息提示
当我们想要使用异常消息国际化提示的时候,比如使用上面定义的AuthException,
只需要如下操作:
throw new AuthException(ExceptionCode.ERROR_JWT_INVALID_SIGNATURE);
可以看到抛出的异常消息最终被国际化,我们将国际化消息的提取封装在了LocalizationException中。
