本文将记录在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:
- FactoryBean是接口,实现该接口的类可以自定义创建Bean。一般在框架中用来创建复杂的Bean。
- 这里的FactoryBean,实现
InitializingBean
接口,在afterPropertiesSet
方法中创建bean,会在 bean 实例化后调用。- 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
实现校验的两种方式:
- 在Controller层的方法参数上,加上
@Validated
注解。(常用) - 使用ValidatorUtils,用编码方式传入实体类进行校验。(在导入导出场景常用)
配合国际化配置使用
假设国际化配置文件中,length.not.valid=长度必须在{min}和{max}
之间,那么我们可以直接这样用:
java
@Size(min=4,max=30,message="length.not.valid")
这样做就能够达到国际化配置的效果。
分组校验
- 需求 :接口A需要校验实体的X字段,接口B需要校验实体的Y字段,而我们直接使用
@Validated
注解,是会全部校验的。 - 分组校验 :通过添加一些无意义的空接口,比如
AddGroup
,UpdateGroup
,QueryGroup
,我们可以用于区分不同的适用场景。 - 具体使用 :
- 首先在实体类的注解上,加上group。比如,
@NotNull(message=xxx,groups={AddGroup.class})
- 如果使用ValidatorUtils :直接在第二个参数传入group的clazz对象,例如
ValidatorUtils.validate(sysUser,AddGroup.class)
- 如果使用
@Validated
,只需要加上@Validated(AddGroup.class)
- 首先在实体类的注解上,加上group。比如,