Spring Boot实现参数校验和异常信息的国际化

你好,我是leo,在之前的文章业务系统国际化方案落地实践中,leo从项目整体的角度分析了国际化项目需要考虑的各种因素,其中提到了对表单信息和异常信息的国际化,可以使用Spring的MessageSource接口来实现。具体应该怎么实现呢,本文就来展开说说。

Spring Boot国际化功能的基础用法

首先来看看Spring Boot如何获取语言参数,以及如何根据语言参数拿到翻译内容
在这里插入图片描述

获取语言参数

从一个http请求中获取参数,无非有这么几种方式:header、url参数、body、cookie。

  1. 从Header获取当前请求的语言

spring默认使用AcceptHeaderLocaleResolver从http请求中解析语言,具体过程为:

public Locale resolveLocale(HttpServletRequest request) {
        Locale defaultLocale = this.getDefaultLocale();
        if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
            return defaultLocale;
        } else {
            Locale requestLocale = request.getLocale();
            List<Locale> supportedLocales = this.getSupportedLocales();
            if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
                Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
                if (supportedLocale != null) {
                    return supportedLocale;
                } else {
                    return defaultLocale != null ? defaultLocale : requestLocale;
                }
            } else {
                return requestLocale;
            }
        }
    }

优先从http的header中获取Accept-Language的值作为当前语言。如果header中没有Accept-Language这个key,就取AcceptHeaderLocaleResolver实例中的默认语言(即defaultLocale,可以手动设置)。如果defaultLocale也为空,就用Locale.getDefault()从操作系统中获取当前语言。

AcceptHeaderLocaleResolver可以手动new一个,定制化默认语言和允许的语言。

@Configuration
public class Config{
    @Bean
    public LocaleResolver localeResolver() {
        AcceptHeaderLocaleResolver acceptHeaderLocaleResolver = new AcceptHeaderLocaleResolver();
        acceptHeaderLocaleResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        //acceptHeaderLocaleResolver.setSupportedLocales();
        return  acceptHeaderLocaleResolver;
    }
}
  1. 从Cookie获取当前请求的语言

spring提供了CookieLocaleResolver类,支持从Cookie中获取语言。Cookie的key可以自定义,使用方式如下:

@Configuration
public class Config{
    @Bean
    public LocaleResolver localeResolver() {
        CookieLocaleResolver localeResolver= new CookieLocaleResolver();
        localeResolver.setCookieName("language");
        return localeResolver;
    }
}

在请求中带上Cookie,如language=en_US,Spring就可以取到传入的语言参数了。

  1. 自定义获取当前请求的语言

如果不想用Header中的Accept-Language传递语言参数,也不想用Cookie传递语言参数,那么可以自定义传递方式,比如换一个header的key,或者把参数放在url,或者放在请求的Body中。示例如下:

@Configuration
public class Config{
    @Bean
    public LocaleResolver localeResolver() {
        return new CustomLocaleResolver();
    }
}
public class CustomLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
    	//从请求参数获取语言
        Locale locale = Locale.forLanguageTag(request.getParameter("language"));
        return locale == null ? Locale.SIMPLIFIED_CHINESE : locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}
根据语言参数获取翻译内容

无论使用以上哪种方式,Locale对象都会保存在LocaleContextHolder中的ThreadLocal里,贯穿当前整个http请求。在当前线程调用的任意方法内,用LocaleContextHolder.getLocale()可以获取Locale对象。

拿到Locale参数后,还需要配置每一种语言对应的翻译。在项目资源文件中建一个资源包
在这里插入图片描述
每种语言对应一个i18nMessage_语言.properties文件,内容为键值对。例如:

i18nMessae_zh_CN.properties文件中内容为
name=张三

i18nMessae_en_US.properties文件中内容为
name=zhangsan

然后在application.properties文件中指定资源包的路径(支持配置多个资源包,用逗号分隔):

spring.messages.basename:i18n/i18nMessage
spring.messages.encoding=UTF-8

最后,用MessageSource的getMessage方法就可以拿到资源包中配置的翻译内容了。示例如下:

@RestController
public class UserController {
    @Resource
    MessageSource messageSource;

    @RequestMapping(value = "/getUserName",method = RequestMethod.GET)
    public String getUserName(){
        Locale locale= LocaleContextHolder.getLocale();
        String userName= messageSource.getMessage("name",null,locale);
        return userName;
    }
}

结合参数验证和自定义业务异常实现错误信息国际化

有了上述的Spring Boot国际化基础功能和用法,就可以结合参数校验和自定义异常,来实现校验结果和异常信息的国际化了。

在这里插入图片描述

参数校验结果国际化

参数的校验使用hibernate-validator组件,在Spring Boot中可以直接添加starter依赖:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>

然后注入一个Validator的Bean,并配置它的国际化翻译源MessageSource,这是实现参数校验国际化的核心步骤。

@Configuration
public class Config{
    @Autowired
    private MessageSource messageSource;
    @Bean
    public Validator validator(){
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setValidationMessageSource(messageSource);
        return validator;
    }
}

校验的字段不能硬编码错误信息,要写code,这个code也就是资源包中键值对的key

@Data
@Valid
public class BusinessModel {
    @NotBlank(message="{name_blank_error}")
    String name;
}

具体的校验方式可以有两种:

  1. 在Spring Boot请求的入口处做参数校验
 @RequestMapping(value = "/getUserName",method = RequestMethod.POST)
    public String getUserName(@Valid @RequestBody BusinessModel model){
    }

在请求的入口处,对body中的数据做校验。校验结果不通过时会抛出BindException异常,因此还要在全局异常中处理异常。

@ControllerAdvice
public class GlobelExceptionHandler {
    @ExceptionHandler({BindException.class})
    @ResponseBody
    public String handleBindException(BindException exception) {
        List<FieldError> allErrors = exception.getFieldErrors();
        StringBuilder sb = new StringBuilder();
        for (FieldError errorMessage : allErrors) {
            sb.append(errorMessage.getDefaultMessage()).append(", ");
        }
        return sb.toString();
    }

在上面这个例子中,所有字段的校验错误保存在allErrors对象里,用FieldError的getDefaultMessage方法可以拿到每个字段的校验错误信息,这个错误信息是根据当前请求的语言翻译后的错误信息。实际项目中,还会对这些错误信息进一步封装,以前后端约定好的接口形式返回给前端。

  1. 在任意地方,对自定义的Model做参数校验

一般来说,接收到前端请求参数后,后端还会组装数据请求下游服务,可能是用http请求(Spring Cloud,或网关),也可能是RPC请求(Dubbo),还可能是MQ(RabbitMQ,Kafka,RocketMQ),假如是RPC请求,那么下游服务对请求参数的校验就无法直接用下面这样的写法来校验了:

public String getUserName(@Valid @RequestBody BusinessModel model){
}

对于这种情况,leo在项目中用手动调用校验接口的方式来校验参数。比如某个方法传入了BusinessModel类型的参数model,那么可以直接调用Validator的validate方法。

@Component
public class Service1 {
    @Resource
    Validator validator;

    public String getUserName(@Valid BusinessModel model){
        val validResult= validator.validate(model);
        StringBuilder sb = new StringBuilder();
        for (ConstraintViolation<?> errorMessage : validResult) {
            sb.append(errorMessage.getMessage()).append(", ");
        }
        return sb.toString();
    }
}

手动调用validate方法后拿到的结果,就是每个字段经翻译后的错误信息了。

异常信息国际化

异常信息国际化的思路是将自定义异常和MessageSource结合起来,返回的异常信息是经过MessageSource翻译后的内容,然后在全局异常处理环节将翻译后的错误信息返回给调用方。

例如,自定义的异常:

@Data
public class ApplicationException extends RuntimeException{

    List<ErrorMsg> errors=new ArrayList<>();

    public ApplicationException addError(String code, Object... args){
        MessageSource messageSource= (MessageSource) SpringBeanUtils.getBean("messageSource");
        String translatedMessage=messageSource.getMessage(code,args, LocaleContextHolder.getLocale());
        this.errors.add(new ErrorMsg(code,translatedMessage));
        return this;
    }

    @Override
    public String getMessage(){
        if(CollectionUtils.isEmpty(this.errors)){
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (ErrorMsg errorMsg : errors) {
            sb.append(errorMsg.getMessage()).append(", ");
        }
        return sb.toString();
    }

    @Data
    @AllArgsConstructor
    public static class ErrorMsg{
        String code;
        String message;
    }
}

在这个自定义异常中,有一个errors的集合,支持一个异常包含多个错误信息,errors中的元素是内部类ErrorMsg。
addError方法添加一个错误信息,code为错误码,要和资源包中的key对应,args为可选的错误信息中的参数。添加错误信息时,根据code翻译为具体的错误文本,放在ErrorMsg中。
getMessage方法重写父类的方法,返回errors集合中的所有错误信息。

业务校验代码示例:

    @RequestMapping(value = "/getUserName",method = RequestMethod.POST)
    public String getUserName(@Valid @RequestBody BusinessModel model){
        //业务逻辑校验不通过抛出ApplicationException异常
        ApplicationException exception=new ApplicationException();
        exception.addError("5001",model.getName());
        throw exception;
    }

异常处理示例:

@ControllerAdvice
public class ExceptionHandler1 {

    @ExceptionHandler({ApplicationException.class})
    @ResponseBody
    public String handleApplicationException(ApplicationException exception) {
        return exception.getMessage();
    }
}

总结

本文是leo之前的文章业务系统国际化方案落地实践的补充。

在本篇文章中,leo先总结了Spring Boot如何从http请求中获取语言参数,如何用MessageSource接口从资源包获取翻译内容,并结合常用的参数校验组件hibernate-validator给出了参数校验国际化的实现步骤,以及结合自定义异常实现了业务异常信息的国际化。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot中,可以使用Spring Boot Validation来对参数名称进行校验。通过引入spring-boot-starter-validation依赖,可以使用Spring Validator对参数进行校验Spring Validator是对Hibernate Validator的进一步封装,同时支持Spring MVC的自动校验。你可以在Spring官方文档中找到更多关于Spring Boot Validation的详细信息\[2\]。 在使用Spring Boot Validation时,可以使用@Validated注解来标记需要校验的类型、方法和方法参数。@Validated注解属于org.springframework.validation.annotation包,是Spring校验机制之一。它具有分组校验的功能,可以用于类型、方法和方法参数上,但不能用于成员属性(field)\[3\]。 通过使用Spring Boot Validation,你可以简化参数校验的代码,提高代码的可读性和美观性。 #### 引用[.reference_title] - *1* *3* [Spring Boot参数校验](https://blog.csdn.net/qq1929892209/article/details/126133350)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Spring Boot 参数校验校验工具类](https://blog.csdn.net/WEDUEST/article/details/121594610)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值