Spring注解`@RestControllerAdvice`实战之响应数据国际化

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);
    }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值