总结摘要
本文介绍了如何在 Spring 中实现自定义数据校验并支持国际化功能。作者通过创建一个校验手机号码的注解 @Mobile 和对应的验证器类 MobileValidator,结合谷歌的 libphonenumber 库实现手机号码的校验逻辑。为了支持国际化,作者创建了多语言文件,并通过自定义的 MessageSource 将其集成到 Spring 的校验系统中。最后,通过配置类 ValidationConfiguration 确保自定义的校验逻辑和国际化功能能够正确加载。使用时,只需在需要校验的字段上添加 @Mobile 注解,并在控制器中使用 @Valid 或 @Validated 即可完成校验。
通常,当我们需要验证用户输入时,Spring MVC提供标准的预定义验证器。我们会引入spring-boot-starter-validation
依赖来实现数据校验功能。
但是,当我们需要验证特定类型的输入时,我们就需要创建自己的自定义校验逻辑。这里我们取一个相对简单的校验手机号码的功能来实现。
为了校验手机号码,我们需要引入谷歌的 libphonenumber
依赖Spring的和spring-boot-starter-validation
:
1
2
3
4
5
6
7
8
| <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.libphonenumber</groupId>
<artifactId>libphonenumber</artifactId>
</dependency>
|
创建注解
我们创建一个新的@interface
来创建注解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
| /**
* 手机号校验
*/
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(List.class)
@Constraint(validatedBy = {MobileValidator.class})
public @interface Mobile {
/**
* 错误消息
*
* @return 错误消息
*/
String message() default "{com.demo.validation.constraints.Mobile.message}";
/**
* 分组
*
* @return 分组
*/
Class<?>[] groups() default {};
/**
* payload
*
* @return payload
*/
Class<? extends Payload>[] payload() default {};
/**
* 默认地域,用于识别手机号
*
* @return 默认地域
*/
String defaultRegion() default "CN";
/**
* 允许的地域列表
*
* @return 允许的地域列表
*/
String[] regions() default {"CN"};
/**
* 列表
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface List {
/**
* value
*
* @return value
*/
Mobile[] value();
}
}
|
使用@Constraint
注解,我们定义了实际用来处理验证字段的类。message()
是显示在用户交互界面中的错误消息。最后,附加代码主要是符合Spring标准的样板代码。
这里的message()
如果你不需要国际化功能,你也可以直接写死,比如“手机号不合法”之类的,但此处我们还想实现国际化功能,还是需要设置成多语言信息的形式。
创建验证类
现在让我们创建一个验证器类来执行我们的验证规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| /**
* 手机号校验
*/
public class MobileValidator implements ConstraintValidator<Mobile, String> {
private String defaultRegion;
private String[] regions;
@Override
public void initialize(Mobile constraintAnnotation) {
defaultRegion = constraintAnnotation.defaultRegion();
regions = constraintAnnotation.regions();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
PhoneNumberUtil phoneUtils = PhoneNumberUtil.getInstance();
try {
PhoneNumber phoneNumber = phoneUtils.parse(value, defaultRegion);
String regionCode = phoneUtils.getRegionCodeForCountryCode(phoneNumber.getCountryCode());
boolean parseRet = Arrays.stream(regions).anyMatch(item -> item.equalsIgnoreCase(regionCode));
if (!parseRet) {
return false;
}
return phoneUtils.isValidNumber(phoneNumber);
} catch (NumberParseException e) {
return false;
}
}
}
|
验证类主要实现了ConstraintValidator
接口,还必须实现isValid
方法;我们正是在这个方法中定义了验证规则。我们这里简单的借助libphonenumber
依赖来帮我们实现手机号的校验。
而在initialize
中我们主要做了注解参数的处理,这里defaultRegion
主要设置默认的号码地域,比如这里是中国大陆CN
,另外一个regions
主要用于设置校验哪些地区的手机号。
创建多语言文件
为了实现国际化功能,我们还需要添加多语言文件,比如我们建在 resources/com/demo/validator/ValidationMessages
目录下,我们新建对应的多语言文件:
ValidationMessages.properties
1
| com.demo.validation.constraints.Mobile.message=必须为格式规范的手机号
|
ValidationMessages_en.properties
1
| com.demo.validation.constraints.Mobile.message=must be a well-formed phone number
|
ValidationMessages_zh_CN.properties
1
| com.demo.validation.constraints.Mobile.message=必须为格式规范的手机号
|
ValidationMessages_zh_TW.properties
1
| com.demo.validation.constraints.Mobile.message=必須是形式完整的電話號碼
|
引入多语言文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| /**
* 参数校验自动配置
*/
@Configuration
@ConditionalOnClass(ExecutableValidator.class)
@AutoConfigureBefore(ValidationAutoConfiguration.class)
public class ValidationConfiguration {
/**
* 校验factory
*
* @param messageSource 错误信息
* @return LocalValidatorFactoryBean
*/
@Bean
@ConditionalOnMissingBean
public LocalValidatorFactoryBean localValidatorFactoryBean(@Qualifier("customValidationMessageSource") MessageSource messageSource) {
LocalValidatorFactoryBean localValidator = new LocalValidatorFactoryBean();
localValidator.setValidationMessageSource(messageSource);
return localValidator;
}
/**
* 参数校验的错误信息源
*
* @return MessageSource
*/
@Bean("customValidationMessageSource")
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.addBasenames("classpath:org.hibernate.validator.ValidationMessages",
"classpath:com/demo/validator/ValidationMessages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
|
这里主要有几点需要关注:
我们使用@AutoConfigureBefore(ValidationAutoConfiguration.class)
要求这个配置类在ValidationAutoConfiguration
之前装配,避免LocalValidatorFactoryBean
已经存在造成冲突。
通过创建自定义的MessageSource
,添加了hibernate
的多语言文件,同时把我们自己的多语言文件也添加进去了。
将LocalValidatorFactoryBean
的ValidationMessageSource设置为我们自定义的MessageSource
。
使用
如果要对数据进行校验,我们只需要在字段上添加注解,然后使用@Valid
或者@Validated
对数据校验即可:
1
2
| @Mobile
private String phone;
|
1
2
3
4
5
6
7
| @Controller
public class ValidatedPhoneController {
@PostMapping("/addValidatePhone")
public String submitForm(@Valid ValidatedPhone validatedPhone) {
}
}
|