配置国际化资源目录
在application.yml里配置message的目录
spring:
messages:
encoding: UTF-8
basename: 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;
}
@Override
public 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
@RestControllerAdvice
public 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中使用。
@Configuration
public class MessageConfig {
/**
* 静态注入messageSource到LocalizationException中
*/
@Bean
@Autowired
public MethodInvokingFactoryBean methodInvokingFactoryBean(MessageSource messageSource) {
MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
methodInvokingFactoryBean.setTargetClass(LocalizationException.class);
methodInvokingFactoryBean.setTargetMethod("setMessageSource");
methodInvokingFactoryBean.setArguments(messageSource);
return methodInvokingFactoryBean;
}
/**
* 自定义messageSource文件的位置,更灵活,此方法可不写,因为application.yml已经配置了
*/
@Bean
public 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中。