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

本文将记录在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的国际化资源文件路径。

yml 复制代码
spring:
	message:
		basename: i18n/messages

国际化配置类

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

  • 注入I18nLocaleResolver国际化解析器 。它实现了LocaleResolver区域解析器。里面有两个方法,只需要重写一个即可
  • 重写resolveLocale方法,大致原理:
    • 拿到Request请求头的content-language,使用下划线分割,分别是语言和国家。
    • 实例化Locale对象并返回。
java 复制代码
@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是在当前线程才有效的,如果切换线程,是不会产生效果的。

java 复制代码
@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是否生效,结果:发现不会生效。

java 复制代码
@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配置

xml 复制代码
<!-- 自定义验证注解 -->  
<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,可以通过实现接口来自定义。
java 复制代码
@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相关注解,对实体类加上相关注解。
java 复制代码
@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}之间,那么我们可以直接这样用:

java 复制代码
@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)
相关推荐
柏油1 小时前
MySQL InnoDB 行锁
数据库·后端·mysql
咖啡调调。1 小时前
使用Django框架表单
后端·python·django
Java&Develop1 小时前
onloyoffice历史版本功能实现,版本恢复功能,编辑器功能实现 springboot+vue2
前端·spring boot·编辑器
白泽talk1 小时前
2个小时1w字| React & Golang 全栈微服务实战
前端·后端·微服务
摆烂工程师1 小时前
全网最详细的5分钟快速申请一个国际 “edu教育邮箱” 的保姆级教程!
前端·后端·程序员
一只叫煤球的猫1 小时前
你真的会用 return 吗?—— 11个值得借鉴的 return 写法
java·后端·代码规范
Asthenia04122 小时前
HTTP调用超时与重试问题分析
后端
颇有几分姿色2 小时前
Spring Boot 读取配置文件的几种方式
java·spring boot·后端
AntBlack2 小时前
别说了别说了 ,Trae 已经在不停优化迭代了
前端·人工智能·后端