配置国际化资源目录

在application.yml里配置message的目录

  1. spring:
  2. messages:
  3. encoding: UTF-8
  4. basename: i18n/msg

image.png
这里我配置在resource目录下的/i18n/msg,里面每个msg.properties就是不同语言对应的消息文字。

定义全局异常消息国际化

我们可以自定义一种抽象异常父类,需要给出国际化消息提示的时候,传入消息的key,然后自动返回国际化的提示。
如果你想要你的异常可以进行国际化的提示,继承这个国际化类就OK。

  1. public abstract class LocalizationException extends RuntimeException {
  2. private static final long serialVersionUID = 7718379187319837197L;
  3. private static MessageSource messageSource;
  4. public LocalizationException(String messageKey, String... messageArguments) {
  5. this(getMessage(messageKey, messageArguments), null, messageKey, messageArguments);
  6. }
  7. public static MessageSource getMessageSource() {
  8. return messageSource;
  9. }
  10. public static void setMessageSource(MessageSource messageSource) {
  11. LocalizationException.messageSource = messageSource;
  12. }
  13. private final String messageKey;
  14. private final String[] messageArguments;
  15. public LocalizationException(String systemErrorMessage, Throwable cause, String messageKey,
  16. String... messageArguments) {
  17. super(systemErrorMessage, cause);
  18. this.messageKey = messageKey;
  19. this.messageArguments = messageArguments;
  20. }
  21. LocalizationException(String systemErrorMessage, String messageKey, String... messageArguments) {
  22. this(systemErrorMessage, null, messageKey, messageArguments);
  23. }
  24. LocalizationException(Throwable cause, String messageKey, String... messageArguments) {
  25. this(cause.getMessage(), cause, messageKey, messageArguments);
  26. }
  27. LocalizationException(Throwable cause) {
  28. super(cause);
  29. this.messageKey = null;
  30. this.messageArguments = null;
  31. }
  32. /**
  33. * @return the messageKey - the key to lookup up in the localized resource bundles, such as
  34. * 'user.not.found'.
  35. */
  36. public String getMessageKey() {
  37. return messageKey;
  38. }
  39. /**
  40. * @return the messageArguments - - an array of arguments that will be filled in for params
  41. * within the message (params look like "{0}", "{1,date}", "{2,time}" within a message),
  42. * or <code>null</code> if none.
  43. */
  44. public Object[] getMessageArguments() {
  45. return messageArguments;
  46. }
  47. @Override
  48. public String getMessage() {
  49. if (StringUtils.isNotBlank(getMessageKey())) {
  50. return messageSource.getMessage(getMessageKey(), getMessageArguments(), getMessageKey(),
  51. LocaleContextHolder.getLocale());
  52. } else {
  53. return super.getMessage();
  54. }
  55. }
  56. private static String getMessage(String messageKey, String... messageArguments) {
  57. if (StringUtils.isNotBlank(messageKey)) {
  58. return messageSource.getMessage(messageKey, messageArguments, messageKey, LocaleContextHolder.getLocale());
  59. } else {
  60. return messageKey;
  61. }
  62. }
  63. }

这里本地化异常LocalizationException里包含了MessageSource国际化资源对象,最终通过把国际化消息的key和arguments传递给messageSource获得国际化的消息。
下面定义一个授权异常子类实现本地化异常父类:

  1. public class AuthException extends LocalizationException {
  2. public AuthException(String messageKey, String... messageArguments) {
  3. super(messageKey, messageArguments);
  4. }
  5. }

之后我们把异常的Key全局统一管理起来,写到一个接口常量中。

  1. public interface ExceptionCode {
  2. String ERROR_JWT_INVALID_SIGNATURE = "error.jwt.invalid.signature";
  3. String ERROR_JWT_INVALID_TOKEN = "error.jwt.invalid.token";
  4. }

配置全局异常拦截

上一步实现了全局异常的消息国际化,下一步配置全局国际化异常的拦截处理。

  1. @Slf4j
  2. @RestControllerAdvice
  3. public class GlobalExceptionHandler {
  4. /**
  5. * 拦截国际化异常
  6. * @param e
  7. * @return
  8. */
  9. @ExceptionHandler(value = {LocalizationException.class})
  10. public Result baseException(LocalizationException e) {
  11. log.error("LocalizationException:{}", e.getMessage());
  12. return Result.fail(e.getMessage());
  13. }
  14. /**
  15. * 拦截RuntimeException
  16. * @param ex
  17. * @return
  18. */
  19. @ExceptionHandler(value = {RuntimeException.class})
  20. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  21. public Result runtimeException(RuntimeException ex) {
  22. log.error("exception:", ex);
  23. return Result.fail(ExceptionUtils.getMessage(ex));
  24. }
  25. /**
  26. * 拦截Exception
  27. * @param ex
  28. * @return
  29. */
  30. @ExceptionHandler(value = {Exception.class})
  31. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  32. public Result exception(Exception ex) {
  33. log.error("exception:", ex);
  34. return Result.fail(ApiErrorCode.FAILED.getMsg());
  35. }
  36. /**
  37. * 在调用RPC、二方包、或动态生成类的相关方法
  38. * @param throwable
  39. * @return
  40. */
  41. @ExceptionHandler(value = {Throwable.class})
  42. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  43. public Result throwable(Throwable throwable) {
  44. log.error("throwable:", throwable);
  45. return Result.fail(ApiErrorCode.FAILED.getMsg());
  46. }
  47. }

重点在于@RestControllerAdvice注解和@ExceptionHandler进行拦截异常。如果没有拦截到的异常可以使用Exception异常兜底。

配置 messageSource Bean

然后我们需要将messageSource配置Bean,并注入到LocalizationException的静态变量messageSource中使用。

  1. @Configuration
  2. public class MessageConfig {
  3. /**
  4. * 静态注入messageSource到LocalizationException中
  5. */
  6. @Bean
  7. @Autowired
  8. public MethodInvokingFactoryBean methodInvokingFactoryBean(MessageSource messageSource) {
  9. MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
  10. methodInvokingFactoryBean.setTargetClass(LocalizationException.class);
  11. methodInvokingFactoryBean.setTargetMethod("setMessageSource");
  12. methodInvokingFactoryBean.setArguments(messageSource);
  13. return methodInvokingFactoryBean;
  14. }
  15. /**
  16. * 自定义messageSource文件的位置,更灵活,此方法可不写,因为application.yml已经配置了
  17. */
  18. @Bean
  19. public ReloadableResourceBundleMessageSource messageSource() {
  20. ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
  21. messageSource.setBasename("classpath:/i18n/msg");
  22. // 设置缓存加载的属性文件的秒数,-1表示要永久缓存(与java.util.ResourceBundle的默认行为匹配)
  23. messageSource.setCacheSeconds(10);
  24. messageSource.setDefaultEncoding(StandardCharsets.UTF_8);
  25. return messageSource;
  26. }
  27. }

ReloadableResourceBundleMessageSource的继承结构:image.png

使用国际化异常消息提示

当我们想要使用异常消息国际化提示的时候,比如使用上面定义的AuthException,
只需要如下操作:

  1. throw new AuthException(ExceptionCode.ERROR_JWT_INVALID_SIGNATURE);

可以看到抛出的异常消息最终被国际化,我们将国际化消息的提取封装在了LocalizationException中。