Spring注解@RestControllerAdvice
实战之国际化
在开发RESTful API时,支持国际化(i18n)是提供多语言支持的关键步骤之一。当API面向全球用户时,提供不同语言环境的错误消息和响应变得尤为重要。Spring框架通过@RestControllerAdvice
结合国际化支持,可以轻松地实现RESTful API的多语言错误处理。
国际化配置
首先,我们需要在Spring Boot项目中配置国际化。这通常涉及到添加消息源文件(通常是.properties
文件),并为每种支持的语言创建一个单独的文件。
例如,假设我们支持英语(默认)和简体中文,我们需要在src/main/resources
目录下创建以下文件:
messages.properties
(默认,英语)messages_zh_CN.properties
(简体中文)
在这些文件中,我们可以定义各种消息键和对应的消息文本。
messages.properties
:
error.userNotFound=User not found
error.invalidRequest=Invalid request
messages_zh_CN.properties
:
error.userNotFound=用户未找到
error.invalidRequest=无效请求
然后,我们需要在Spring Boot的配置类中配置消息源(MessageSource
)和区域解析器(LocaleResolver
)。
@Configuration
public class I18nConfig {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
// 设置缓存时间,开发中可以设置为0,表示不缓存
messageSource.setCacheSeconds(0);
return messageSource;
}
@Bean
public LocaleResolver localeResolver() {
// 这里可以使用AcceptHeaderLocaleResolver或CookieLocaleResolver等来实现基于HTTP头或Cookie的区域解析
// 这里简单起见,我们使用AcceptHeaderLocaleResolver
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(Locale.US); // 默认使用美国英语
return localeResolver;
}
}
使用@RestControllerAdvice
和国际化
接下来,我们可以使用@RestControllerAdvice
来全局处理异常,并结合国际化功能来返回对应语言环境的错误消息。
@RestControllerAdvice
public class GlobalExceptionHandler {
@Autowired
private MessageSource messageSource;
@ExceptionHandler(value = CustomException.class)
public ResponseEntity<Object> handleCustomException(CustomException e, Locale locale) {
// 从消息源中获取对应语言环境的错误消息
String errorMessage = messageSource.getMessage(e.getMessageKey(), null, locale);
// 构建错误响应体
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("code", e.getErrorCode());
errorResponse.put("message", errorMessage);
// 返回错误响应
return new ResponseEntity<>(errorResponse, HttpStatus.valueOf(e.getErrorCode()));
}
// ... 其他异常处理方法
}
请注意,在上面的代码中,我们假设CustomException
类有一个getMessageKey()
方法,用于返回消息键(例如error.userNotFound
)。我们还需要修改CustomException
类以支持这一变化。
public class CustomException extends RuntimeException {
private int errorCode;
private String messageKey; // 使用消息键而不是直接的消息文本
public CustomException(int errorCode, String messageKey) {
super(messageKey); // 这里只是为了传递消息键给父类,实际不会使用它
this.errorCode = errorCode;
this.messageKey = messageKey;
}
// getter和setter方法
// ...
}
实战应用
在Controller中,当需要抛出异常时,我们可以使用带有消息键的自定义异常。
@RestController
@RequestMapping("/users")
public class UserController {
// 假设有一个UserService
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
// 抛出自定义异常,使用消息键而不是直接的消息文本
throw new UserNotFoundException(404, "error.userNotFound");
}
return user;
}
// 其他接口...
}
后序:
如果要实现全局消息的国际化,同理可以通过实现接口 ResponseBodyAdvice 的方式
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private MessageSource messageSource;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
if (o instanceof Result) {
Result r = (Result) o;
if (!r.isSuccess()) {
if (r.getCode() == Code.ILLEGAL_PARAM) {
if (r.getMessage() != null) {
r.setMessage(i18nHelper.getMessage(r.getCode()) + (r.getMessage() == null ? "" : "," + r.getMessage()));
} else {
r.setMessage(i18nHelper.getMessage(r.getCode()));
}
} else if (null == r.getMessage()) {
String message = i18nHelper.getMessage(r.getCode());
if (null != message) {
r.setMessage(r.getData() == null ? i18nHelper.getMessage(r.getCode()) : String.format(i18nHelper.getMessage(r.getCode()), r.getData()));
}
}
}
}
return o;
}
}
I18nHelper类具体实现:
public final class I18nHelper {
@Resource
private LocaleResolver localeResolver;
@Resource
private MessageSource messageSource;
@Resource
private HttpServletRequest request;
public String getMessage(HttpServletRequest request, int code, Object... args) {
return messageSource.getMessage(Integer.toString(code), args, "", localeResolver.resolveLocale(request));
}
public String getMessage(int code, Object... args) {
return messageSource.getMessage(Integer.toString(code), args, "", resolveLocale());
}
public String getMessage(String key, Object... args) {
return messageSource.getMessage(key, args, "", resolveLocale());
}
private Locale resolveLocale() {
if (request == null) {
return Locale.ENGLISH;
}
return localeResolver.resolveLocale(request);
}
}