likes
comments
collection
share

开发札记:Validator注解配合国际化

作者站长头像
站长
· 阅读数 5

本文将记录在SpringBoot开发中如何配置国际化,如何配置Validator数据校验,以及如何将两者配合在一起使用。

国际化配置

什么是i18n目录

i18n目录内含三个配置文件:(全称是internationalization,国际化)

  • messages.properties
  • messages_en_US.properties
  • message_zh_CN.properties

这些配置文件维护了一些键值对,如not.null=* 必须填写等,方便我们在异常处理、日志打印、数据校验等场景下返回对应语言的错误消息。国家化配置还可以结合Validator注解进行使用,如user.username.length.valid=账户长度必须在{min}到{max}个字符之间,将在下文讲解如何配置。

如果需要配置其它语言,可以按照国际化资源文件命名规范命名。如日本:ja_JP等,然后写上对应语言的提示消息即可。

Yml文件配置

配置Spring的国际化资源文件路径。

spring:
	message:
		basename: i18n/messages

国际化配置类

国际化配置类I18nConfig主要做了以下几件事情:

  • 注入I18nLocaleResolver国际化解析器。它实现了LocaleResolver区域解析器。里面有两个方法,只需要重写一个即可
  • 重写resolveLocale方法,大致原理:
    • 拿到Request请求头的content-language,使用下划线分割,分别是语言和国家。
    • 实例化Locale对象并返回。
@Configuration  
public class I18nConfig {  
    @Bean  
    public LocaleResolver localeResolver() {  
        return new I18nLocaleResolver();  
    }  
  
    /**  
     * 获取请求头国际化信息  
     */  
    static class I18nLocaleResolver implements LocaleResolver {  
  
        @Override  
        public Locale resolveLocale(HttpServletRequest httpServletRequest) {  
            String language = httpServletRequest.getHeader("content-language");  
            Locale locale = Locale.getDefault();  
            if (StrUtil.isNotBlank(language)) {  
                String[] split = language.split("_");  
                locale = new Locale(split[0], split[1]);  
            }  
            return locale;  
        }  
  
        @Override  
        public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {  
  
        }  
    }  
}

封装国际化工具类

我们还可以封装一个国际化工具类,可用于记录日志的时候获取直接获取国际化内的报错信息。

具体步骤:

  • 从容器拿到MessageSource。
  • 封装一个静态方法,调用MessageSource的getMessage方法,传入code、参数、地区即可获取国际化消息。其中code对应properties文件中的key,args对应消息中的占位符。

值得一提的的是:LocaleContextHolder是Spring的区域上下文持有者,在i18n配置文件中我们会将请求头的Locale注入到上下文,这样这里就可以拿得到。查看源码我们可以得知LocaleContextHolder是和当前线程关联的,这就意味着MessageUtils是在当前线程才有效的,如果切换线程,是不会产生效果的。

@NoArgsConstructor(access = AccessLevel.PRIVATE)  
public class MessageUtils {  
  
    private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class);  
  
    /**  
     * 根据消息键和参数 获取消息 委托给spring messageSource  
     *     * @param code 消息键  
     * @param args 参数  
     * @return 获取国际化翻译值  
     */  
    public static String message(String code, Object... args) {  
        return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());  
    }  
}

实验:引入线程池,观察MessageUtils是否生效,结果:发现不会生效。

@RestController
@RequiredArgsConstructor
public class TestController{
	private final ThreadPoolTaskExecutor threadPoolTaskExecutor;

	public String msg4(){
		Future<String> submit = threadPoolTaskExecutor.submit(()->{
			String msg = MessageUtils.message("user.username.length.valid","1","0");
			log.info("msg:{}",msg);
			return message;
		});		
		return submit.get();
	}
}

应用场景

  • 异常抛出中,用国际化返回错误信息
    • 例如:throw new UserException("user.not.exists",username)
    • 这里有个不错的实践经验:我们在异常的基类中重写了getMessage方法,使用MessageUtils通过国际化返回对应的错误信息
  • 打印日志中,可以用国际化的key方便地打印错误信息
    • 可以配合占位符替换参数,只需要传入到MessageUtils内的args数组即可。
    • 在国际化的properties文件中,用{0}{1}等作为参数替换。
    • 比如:MessageUtils.message("user.not.exists","ajaxzhan"),就会将{0}替换为ajaxzhan并返回。
  • 配合Validator框架使用,比如用户长度必须在{min}到{max}个字符之间,就使用min和max来替换。

Validator校验框架

JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。为了避免在业务代码中参杂过多验证代码,我们一般使用Validation提供的相关注解进行校验。下面演示如何在SpringBoot项目中集成Validation相关功能。

Maven配置

<!-- 自定义验证注解 -->  
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-validation</artifactId>  
</dependency>

ValidatorConfig配置类

  • 注入MessageSource,用于国际化配置。
  • 实例化工厂LocalValidatorFactoryBean,设置:
    • 设置国际化:将MessageSource设置到ValidationMessageSource
    • 设置提供者类(校验器)HibernateValidator
    • 设置属性:实例化Properties配置Hibernate的快速异常返回hibernate.validator.fail_fast,加入到工厂配置。(快速返回指的是遇到一个不合法的,就不继续往下校验。)
    • 加载配置:调用factoryBean的afterPropertiesSet
  • 返回工厂方法的Validator

这里可以顺便谈谈关于Spring的FactoryBean

  1. FactoryBean是接口,实现该接口的类可以自定义创建Bean。一般在框架中用来创建复杂的Bean。
  2. 这里的FactoryBean,实现InitializingBean接口,在afterPropertiesSet方法中创建bean,会在 bean 实例化后调用。
  3. FactoryBean让Bean构建过程更灵活,可以理解为一种策略模式,我们需要生成什么样的 bean,可以通过实现接口来自定义。
@Configuration  
public class ValidatorConfig {  
  
    @Autowired  
    private MessageSource messageSource;  
  
    /**  
     * 配置校验框架 快速返回模式  
     */  
    @Bean  
    public Validator validator() {  
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();  
        // 国际化  
        factoryBean.setValidationMessageSource(messageSource);  
        // 设置使用 HibernateValidator 校验器  
        factoryBean.setProviderClass(HibernateValidator.class);  
        Properties properties = new Properties();  
        // 设置 快速异常返回  
        properties.setProperty("hibernate.validator.fail_fast", "true");  
        factoryBean.setValidationProperties(properties);  
        // 加载配置  
        factoryBean.afterPropertiesSet();  
        return factoryBean.getValidator();  
    }
}

封装ValidatorUtils工具类

  • 从IoC容器拿到Validator
  • 调用validate方法进行校验。
  • 需要配合javax.validation相关注解,对实体类加上相关注解。
@NoArgsConstructor(access = AccessLevel.PRIVATE)  
public class ValidatorUtils {  
  
    private static final Validator VALID = SpringUtils.getBean(Validator.class);  
  
    public static <T> void validate(T object, Class<?>... groups) {  
        Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);  
        if (!validate.isEmpty()) {  
            throw new ConstraintViolationException("参数校验异常", validate);  
        }  
    }  
  
}

Validation常见注解:(用于实体类的field上,加多个注解为“且”的关系)

  • @NotBlank(message = "xxx不能为空"),只能用于String类型
  • @Size(min=0,max=30,message="xxx长度必须在{min}和{max}个字符之间")
  • @NotNull(message="ID不能为空")
  • @NotEmpty:用于集合、String类不能为空且size>0
  • @Length:String类型
  • @Pattern(regexp="正则表达式",message="首字母必须大写"):正则
  • @Email:邮箱
  • @Min(value=0,message="")
  • @Max
  • @AssertTrue

实现校验的两种方式

  1. 在Controller层的方法参数上,加上@Validated注解。(常用)
  2. 使用ValidatorUtils,用编码方式传入实体类进行校验。(在导入导出场景常用)

配合国际化配置使用

假设国际化配置文件中,length.not.valid=长度必须在{min}和{max}之间,那么我们可以直接这样用:

@Size(min=4,max=30,message="length.not.valid")

这样做就能够达到国际化配置的效果。

分组校验

  • 需求:接口A需要校验实体的X字段,接口B需要校验实体的Y字段,而我们直接使用@Validated注解,是会全部校验的。
  • 分组校验:通过添加一些无意义的空接口,比如AddGroupUpdateGroupQueryGroup,我们可以用于区分不同的适用场景。
  • 具体使用
    • 首先在实体类的注解上,加上group。比如,@NotNull(message=xxx,groups={AddGroup.class})
    • 如果使用ValidatorUtils:直接在第二个参数传入group的clazz对象,例如ValidatorUtils.validate(sysUser,AddGroup.class)
    • 如果使用@Validated,只需要加上@Validated(AddGroup.class)

参考文章

  1. FactoryBean深入浅出-CSDN博客
  2. SpringBoot国际化配置-CSDN博客
  3. RuoYi-Vue-Plus: 后台管理系统 (gitee.com)