Spring MVC 之 自定义参数校验(JSR 303 - Bean Validation)

系统提供对外接口或公用方法时,通常需要对入参校验,即直接在接口入口层就进行校验,不让非法参数流入到业务层导致异常,确保系统的健壮性。。

数据校验分客户端校验和服务端校验,客户端校验主要在页面通过JavaScript来实现,过滤正常用户的误操作,仅做初步过滤;服务端校验是整个应用阻止非法数据的最后防线,客户端校验绝不能替代服务端的校验,客户端校验可以降低服务器的负载。

Java EE 6 开始定义了一项为校验 Bean 数据合法性的规范 JSR-303,叫做 Bean Validation,该标准目标有两个实现:Hibernate ValidatorApache bval,使用较多的是前者。

Hibernate Validator (与 Hibernate ORM 没有关系)是 Bean Validation 的参考实现, 提供了 JSR-303 规范中所有内置 constraint 的实现,并扩展了一些常需要使用的 constraint,提供了一套比较完善,便捷的验证实现方式。

Validation

JSR-303

Bean Validation为 Java Bean 定义了相应的数据类型和 API,在应用中通过在Bean属性上标注类似于@NotNull, @Max等标准的注解指定校验规则,并通过验证接口对 Bean 进行验证。

该校验框架是一个运行时框架,在验证之后验证的错误信息被马上返回;核心接口是javax.validation.Validator,该接口根据目标对象类中所标注的校验注解进和地数据校验,并得到校验结果。

内置校验注解

JSR-303(Bean Validation)内置的 constraint(约束):

Constraint 描述 备注
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null 通常作用在对象属性上,
作用在 String 类型上无法校验空格
@NotEmpty 被注释的字符串的必须非空
@NotBlank 被注释的字符串必须不为 null,且必须至少包含一个非空白字符
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期或时间
@PastOrPresent 被注释的元素必须是当前或一个过去的日期或时间
@Future 被注释的元素必须是一个将来的日期或时间
@FutureOrPresent 被注释的元素必须是当前或一个将来的日期或时间
@Pattern(value) 被注释的元素必须符合指定的正则表达式
@Positive 被注释的元素必须是一个严格的正数 0 为非法值
@PositiveOrZero 被注释的元素必须是一个正数或为 0
@Negative 被注释的元素必须是一个严格的负数 0 为非法值
@NegativeOrZero 被注释的元素必须是一个负数或为 0
@Length 被注释的字符串的大小必须在指定的范围内
@Email 被注释的元素必须是电子邮箱地址
@Range 被注释的元素必须在合适的范围内

Hibernate Validator

Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。更多信息,请查看:Hibernate Validator - The Bean Validation reference implementation.Hibernate Validator 6.1.5.Final - Jakarta Bean Validation Reference Implementation: Reference Guide

扩展校验注解

Constraint 描述 备注
@Length 被注释的字符串的长度必须在 minmax 之间
@URL 被注释的字符串必须一个 URL
@Range 被注释的元素必须在合适的范围内 应用于数值或数值的字符串表示形式
@CreditCardNumber 被注释的字符串必须表示有效的信用卡号码 这是Luhn算法的实现,目的是检查用户的错误,
而不是信用卡的有效性!
@LuhnCheck Luhn 算法检查约束 允许验证一系列数字是否通过Luhn模10校验和算法,
Luhn Mod10的计算方法是将数字相加,
每个奇数位(从右到左)的值乘以2,如果值大于9,
则结果数字a在总和之前求和。
@Mod10Check 允许验证一系列数字是否通过Mod10校验和算法 经典的Mod10是通过将数字相加来计算的,
每一个奇数(从右到左)值乘以一个乘数 。
例如,ISBN-13是模10校验和(乘数是 3)。
@Mod11Check 允许验证一系列数字是否通过Mod11校验和算法。 对于最常见的Mod11变量,求和是通过将最右边的数字(不包括校验位)乘以最左边的一个权重来完成的。
权重从2开始,每位数增加1。然后使用(sum%11)计算校验位。
@ISBN 被注释的字符串必须是一个 ISBN ISBN:指国际标准书号,
专为识别图书等文南而设计的国际编号
@UniqueElements 验证所提供的 Collection中的每个对象是否唯一 即在集合中找不到2个相等的元素。
@ConstraintComposition 布尔合成运算约束 值为 CompositionType 枚举(OR,AND,ALL_FALSE)
@NotBlank 已弃用
@NotEmpty 已弃用
@Email 已弃用

校验返回策略

Hibernate Validator 提供了两种校验策略模式:

  • 普通模式:默认,会校验完所有的属性,然后返回所有的验证失败信息。
  • 快速失败返回模式:只要有一个验证失败,就立即返回失败信息。

配置方式:addProperty true 为快速失败返回模式,false 为普通模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class ValidatorConfig {

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
// 设置validator模式为快速失败返回模式
postProcessor.setValidator(validator());
return postProcessor;
}

@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.addProperty(HibernateValidatorConfiguration.FAIL_FAST, "true")
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}

手动使用 Validator 进行校验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data // Lombok
public class User {
@NotBlank(message = "姓名不能为空")
private String name;
}

@ResponseBody
@GetMapping("/user")
public User user() {
User user = new User();
user.setName("");
Set<ConstraintViolation<User>> violationSet = validator.validate(user);
for (ConstraintViolation<User> item : violationSet) {
logger.error("ErrorMessage:{}", item.getMessage());
}
return user;
}

Spring Validator

Spring 提供了自己的数据校验框架。Spring 在进行数据绑定时,可同时调用校验框架来完成数据校验的工作。

Spring 的校验框架在org.springframework.validation包中,重要的接口和类如下:

  • Validator:最重要的接口,里面有两个方法。

  • Errors:存放错误信息的接口。
    校验的结果必须是Errors或是BindingResult类型。

  • ValidatorUtils:校验的工具类,提供了多个Errors对象保存错误的方法。

  • LocalValidatorFactoryBean:该类实现了SpringValidator接口,也实现了JSR 303Validator接口,只要在 Spring 容器中定义一个 LocalValidatorFactoryBean,即可将其注入到需要数据校验的Bean中。

    1
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />

    <mvc:annotation-driven/>会默认装配好一个LocalValidatorFactoryBean,实现开发中不需要手动配置。

Spring MVC 提供的数据校验是通过硬编码完成的,实现开发推荐使用JSR 303来进行数据校验。

Spring boot

如果是 Spring Boot 开发 Web 项目,spring-boot-starter-web 中已经含了 spring-boot-starter-validation包,所以不需要重复添加依赖。

如果没有用 Web 依赖想要使用 Validator 实现 Bean 验证,则添加 spring-boot-starter-validation 依赖即可,该依赖内部引入的是 hibernate-validator 实现校验。

spring-boot-starter-validation 包的依赖关系:

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
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.2.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<!--Bean Validation, JSR 380-->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>2.0.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>9.0.33</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.18.Final</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>validation-api</artifactId>
<groupId>javax.validation</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

添加依赖

Spring Boot 项目添加 spring-boot-starter-validation 依赖,该依赖使用 Hibernate Validator 对 Java Bean 进行校验操作。

1
2
3
4
5
<!--校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Java Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data // Lombok
public class User {

@NotBlank(message = "姓名不能为空")
private String name;

@NotBlank(message = "年龄不能为空")
@Range(min = 18, max = 120, message = "年龄只能从18-120岁")
private String age;

// 日期正则校验
@Pattern(regexp = "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", message = "出生日期格式不正确")
private String birthday;

}

开启校验

在 Java Bean 类上 或 属性上添加验证注解,还需要在 Spring MVC Controller 方法入参使用 @Validated@Valid 注解开启校验。

  • @Valid:javax.validation 提供,作用在方法,构造方法,参数,成员属性上。

    @Valid 可做嵌套校验,作用在属性上,属性是一个 Java Bean,需要对属性 Bean 的内部属性进行校验。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Data // Lombok
    public class Father {
    @NotBlank(message = "name不能为空")
    private String name;
    @Valid
    private List<Son> sonList;
    }

    @Data
    public class Son {
    @NotNull(message = "age不能为null")
    private Integer age;
    }
  • @Validated:Spring 提供(扩展了 @Valid),作用在类,方法,参数上。

    @Validated 支持分组,在接口形参上加上 @Validated({A.class}),只对该分组有效。例如,(@NotBlank(group={A.class},message = “name不能为空”)

    注:A.class 可以是个空类,仅仅用于分组标识。

入参是 Java Bean

接口入参是 Java Bean 方式,@Valid 和 @Validated 都能起效。

1
2
3
4
@PostMapping("/user/query")
public ResponseModel<?> query(@RequestBody @Validated User user) throws Exception {
return "省略";
}

入参非 Java Bean

此方式在参数前使用 @Valid@Validated 都不能起效,只能使用 @Validated 并注释在此方法所在的 Controller 类上才能起效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Validated
@RestController
@RequestMapping("/user")
public class UserController{

@PostMapping("/query")
public ResponseModel<?> query(@RequestBody @NotBlank(message = "name不能为空") String name) throws Exception {
return "省略";
}

@GetMapping(value = "/get")
public ResponseModel<?> getUser(
@Min(value = 18, message = "age最小只能18")
@Max(value = 120, message = "age最大只能120")
@RequestParam(name = "age", required = true) Integer age) {

return age;
}
}

此方式验证抛出的异常为:ConstraintViolationException,可使用全局异常统一处理。见下面 异常统一处理 章节。

验证失败处理

返回验证失败时的错误信息(例如,通过统一异常处理捕获验证异常,取出验证的错误信息)。

绑定验证结果

在验证入参的方法中,可绑定入参校验的结果对象,判断结果对象是否存在错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@PostMapping("/user/query")
public ResponseModel<?> query(@RequestBody @Valid User user, BindingResult result) throws Exception {
if (result.hasErrors()) {
// 校验失败,返回失败
List<FieldError> errors = result.getFieldErrors();
Map<String, Object> map = new HashMap<String, Object>();
for (FieldError error : errors) {
logger.warn("Field:{}, Message:{}", error.getField(), error.getDefaultMessage())
map.put(error.getField(), error.getDefaultMessage());
}
return ResponseModel.fail(map);
}
return "......省略....."
}

异常统一处理

Spring Boot 使用 @ControllerAdvice 进行统一异常处理,Spring MVC 框架抛出方法参数校验异常:MethodArgumentNotValidException,取出异常中的错误信息返回给客户端。

全局异常统一处理参考:Spring Boot 2实践系列(三十八):全局异常统一处理

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
if (e instanceof MethodArgumentNotValidException) {
// 参数校验异常
BindingResult result = ((MethodArgumentNotValidException) e).getBindingResult();
if (result.hasErrors()) {
List<ObjectError> errorList = result.getAllErrors();
for (ObjectError error : errorList) {
return ResponseModel.methodArgumentNotValid(error.getDefaultMessage());
}
}
}

// 或
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseModel<?> methodArgValidationError(MethodArgumentNotValidException e) {
BindingResult result = e.getBindingResult();
final List<FieldError> fieldErrors = result.getFieldErrors();
StringBuilder sb = new StringBuilder();
for (FieldError error : fieldErrors) {
error.getField(), error.getDefaultMessage()
sb.append("Field:" + error.getField()).append(",").append("Message:" + error.getDefaultMessage());
}
logger.warn(sb.toString());
return ResponseModel.fail(sb.toString());
}

/**
* 处理 @Validated 作用在 Controller 类上, 方法参数使用校验注解
*/
@ResponseBody
@ExceptionHandler(ConstraintViolationException.class)
public ResponseModel<?> validationError(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violationSet = e.getConstraintViolations();
StringBuilder sb = new StringBuilder();
for (ConstraintViolation<?> item : violationSet) {
sb.append( "ErrorMessage" + item.getMessage() + "\n");
}
logger.erwarnror(sb.toString());
return ResponseModel.fail(sb.toString());
}

Spring 默认提供了一个配置校验器工厂 Bean,具体参考 SpringMVC-Validation数据校验

自定义校验注解

框架提供的校验注解(constraint)可能并不能完全满足需求, 所以需要自定义校验规则。例如对 IP 地址进行校验,对密码复杂度进行校验等。

Java 的注解使用,可参考 Java基础:Java 注解(Annotation)及使用

校验注解由两部分组成:

  • 校验注解:就一个普通的 Java 注解,但多个了元注解 @Constraint 用于指定该注解的 校验器。见下面章节的示例。

    1
    @Constraint(validatedBy = {xxxxx.class})

    validatedBy 的值指定为校验逻辑的实现类,即校验器。

    自定义校验注解必须包含 message,groups,payload 属性。

  • 校验器:对校验注解进行解析,实现 ConstraintValidator接口,接口使用了泛型,需要指定两个参数,第一个是自定义注解类,第二个是需要校验的数据类型,重写 isValid(T value, ConstraintValidatorContext context)方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public interface ConstraintValidator<A extends Annotation, T> {

    /**
    * 在调用 isValid 方法之前初始化
    * 该方法的参数类型为自定义注解,可调用注解的方法获取注解信息
    * 重写该方法, 给自定义校验器中定义的类变量赋值
    */
    default void initialize(A constraintAnnotation) {
    }

    /**
    * 重写该方法,实现校验逻辑,返回 布尔值
    */
    boolean isValid(T value, ConstraintValidatorContext context);
    }

    自定义注解校验器类实现了 ConstraintValidator 接口,默认会被 Spring 注册成 bean,所以可以在这个校验器类里面使用@Autowired 或者 @Resource 注入别的服务,不需要在类上添加 @Component 注解声明为 Spring 的 bean。

备注:自定义注解只能作用在接口 或 Controller 方法上才能启效,若写在实现类上是不启效的,只支持 Java Bean 验证。

自定义校验示例

金额精确2位小数

校验注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @desc: 金额数字校验
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {AmountNumValidator.class})
public @interface AmountNum {

String message() default "金额数字只精确到两位小数";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}

注解解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @desc: 金额数字校验
*/
public class AmountNumValidator implements ConstraintValidator<AmountNum, BigDecimal> {

// 判断小数点后2位的数字的正则表达式
Pattern pattern = Pattern.compile("^(([1-9]{1}\\d*)|([0]{1}))(\\.(\\d){0,2})?$");

@Override
public boolean isValid(BigDecimal amount, ConstraintValidatorContext context) {
if (Objects.isNull(amount)) {
return true;
}
Matcher match = pattern.matcher(amount.toString());
return match.matches();
}
}

保留小数位校验

校验注解

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
/**
* @desc 小数最大位数限制
*/
@Documented
@Target({ FIELD, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Repeatable(List.class)
@Constraint(validatedBy = {DecimalPlaceMaxValidtor.class})
public @interface DecimalPlaceMax {

int places();

String message() default "";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

/**
* Defines several {@link DecimalPlaceMax} annotations on the same element.
*/
@Target({ FIELD, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@interface List {
DecimalPlaceMax[] value();
}
}

注解解析器

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
/**
* @desc 最大小数位数校验器
*/
public class DecimalPlaceMaxValidtor implements ConstraintValidator<DecimalPlaceMax, Number> {

int places; // 最大小数位数

@Override
public void initialize(DecimalPlaceMax constraintAnnotation) {
this.places = constraintAnnotation.places();
}

@Override
public boolean isValid(Number value, ConstraintValidatorContext context) {
if (Objects.isNull(value)) {
return true;
}
String valueStr = String.valueOf(value);
// Symbol.POINT = "."
if (StringUtil.indexOf(valueStr, Symbol.POINT) < 0) {
return true;
}
return StringUtil.split(valueStr, Symbol.POINT)[1].length() <= places;
}
}

字符串是否在数组中

校验注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @desc 字符串是否属于定义的字符串数组中一个判断
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { NotInStringsValidtor.class })
@Documented
public @interface NotInStrings {
String message() default "请输入正确参数";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

String[] values() default {};
}

注解解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.apache.commons.lang3.StringUtils;

/**
* @desc 字符串是否属于字符串数组中一个判断
*/
public class NotInStringsValidtor implements ConstraintValidator<NotInStrings, String> {

String[] values; // 字符串数组

@Override
public void initialize(NotInStrings constraintAnnotation) {
this.values = constraintAnnotation.values();
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return StringUtils.equalsAny(value, values);
}
}

字符串是否在枚举中

校验注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @desc 字符串是否属于枚举类型中一个判断
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { NotEnumIncludeValidtor.class })
@Documented
public @interface NotEnumInclude {
String message() default "";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

Class<?>[] target() default {};

String methodName() default "name";
}

注解解析器

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
import org.apache.commons.lang3.StringUtils;
/**
* @desc 字符串是否属于枚举类型中一个判断
*/
@Slf4j
public class NotEnumIncludeValidtor implements ConstraintValidator<NotEnumInclude, String> {

Class<?>[] cls; // 枚举类
String methodName;

@Override
public void initialize(NotEnumInclude constraintAnnotation) {
cls = constraintAnnotation.target();
this.methodName = constraintAnnotation.methodName();
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (StringUtils.isBlank(value)) {
return true;
}
if (cls.length > 0) {
for (Class<?> cl : cls) {
try {
if (cl.isEnum()) {
// 枚举类验证
Object[] objs = cl.getEnumConstants();
Method method = cl.getMethod(methodName);
for (Object obj : objs) {
Object code = method.invoke(obj);
if (value.equalsIgnoreCase(code.toString())) {
return true;
}
}
}
} catch (Exception ex) {
log.error("catch an error when validate enum include", ex);
}
}
} else {
return true;
}
return false;
}
}

数字是否在枚举中

校验注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Target({ElementType.FIELD, ElementType.PARAMETER})  
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = IntegerEnumValidator.class)
public @interface IntegerEnum {

String message() default "invalid number";

int[] values() default {};

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

}

注解解析器

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
public class IntegerEnumValidator implements ConstraintValidator<IntegerEnum, Integer> {

private IntegerEnum integerEnum;

@Override
public void initialize(IntegerEnum constraintAnnotation) {
this.integerEnum = constraintAnnotation;
}

@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
int[] values = integerEnum.values();
if (values.length == 0) {
return true;
}
for (int v : values) {
if (value == v) {
return true;
}
}
return false;
}
}

注解使用

在实例类的属性上使用 @IntegerEnum 注解,指定枚举值。Controller 方法的入参增加 @Validated 注解使校验起效。

1
2
3
4
5
6
@Data  
public class User {
private Long id;
@IntegerEnum(values = {1, 2, 3}, message = "类型错误")
private Integer type;
}

校验使用示例

  1. pom.xml文件引入Hibernate Validator jar包和依赖包。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- JSR 303数据校验实现:hibernate-validator -->
    <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.7.Final</version>
    </dependency>
    <!-- javax.el -->
    <dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>2.2.5</version>
    </dependency>
    <dependency>
    <groupId>org.glassfish.web</groupId>
    <artifactId>javax.el</artifactId>
    <version>2.2.5</version>
    </dependency>
  2. 实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class People {

    private String name;
    @NotBlank(message="登录名不能为空")
    private String loginName;
    @Length(min=6,max=12,message="密码长度必须在6位到8位之前")
    private String password;
    @NotBlank(message="用户名不能为空")
    private String userName;
    @Range(min=15,max=60,message="年龄必须在15到60岁之间")
    private double age;
    @Email(message="请输入合法邮箱地址")
    @NotBlank(message="邮箱地址不能为空")
    private String email;
    @DateTimeFormat(pattern="yyyy-MM-dd")
    @Past(message="生日必须是一个过去的日期")
    private Date birthDay;
    @Pattern(regexp="[1][3,8][3,6,9][0-9] {8}",message="无效电话号码")
    private String phone;

    //-------set/get方法------------

  3. Controller代码
    方法中接收参数的对象添加@Valid注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Controller
    @RequestMapping("/user")
    public class LoginController {

    @RequestMapping(value = "/registerForm")
    public String registerForm() {
    return "registerForm";
    }

    @RequestMapping(value="/login", method=RequestMethod.POST)
    public String login(@Valid People people, Errors errors, Model model) {
    System.err.println(people);
    System.err.println(errors);
    if(errors.hasErrors()) {
    String defaultMessage = errors.getFieldError("age").getDefaultMessage();
    System.out.println(defaultMessage);
    return "registerForm";
    }
    model.addAttribute("people", people);
    return "success";
    }
    }

  4. JSP表单代码

    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
    <%@ page language="java" contentType="text/html; charset=UTF-8"	pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <script type="text/javascript"
    src="${pageContext.request.contextPath}/static/js/jquery-1.11.0.js"></script>

    <title>JSR 303 Validation</title>
    </head>
    <body>
    <h3>注册页面</h3>
    <form action="${pageContext.request.contextPath}/user/login"
    method="post">
    <table>
    <tr>
    <td>登录名:</td>
    <td><input name="loginName"></td>
    </tr>
    <tr>
    <td>密码:</td>
    <td><input name="password"></td>
    </tr>
    <tr>
    <td>用户名:</td>
    <td><input name="userName"></td>
    </tr>
    <tr>
    <td>年龄:</td>
    <td><input name="age"></td>
    </tr>
    <tr>
    <td>邮箱:</td>
    <td><input name="email"></td>
    </tr>
    <tr>
    <td>生日:</td>
    <td><input name="birthDay"></td>
    </tr>
    <tr>
    <td>电话:</td>
    <td><input name="phone"></td>
    </tr>
    <tr>
    <td><input type="submit" value="提交"></td>
    </tr>
    </table>

    </form>
    </body>
    </html>

相关参考

  1. JSR 303 - Bean Validation 介绍及最佳实践
  2. Hibernate Validator:Bean Validation 的参考实现
  3. Hibernate Validator 6.1.5.Final - Jakarta Bean Validation Reference Implementation: Reference Guide
  4. JSR 303: Bean Validation
  5. JSR 380: Bean Validation 2.0
  6. Jakarta Bean Validation
  7. Jakarta-bean-validation 校验示例
  8. Validator 自动化校验
  9. Spring/Spring boot JSR-303验证框架 之 hibernate-validator
  10. Hibernate Validator 使用介绍
  11. 参数校验工具之Validator自定义校验

Spring MVC 之 自定义参数校验(JSR 303 - Bean Validation)

http://blog.gxitsky.com/2020/07/02/SpringMVC-37-JSR-303-BeanValidation-Hibernate-Validator/

作者

光星

发布于

2020-07-02

更新于

2022-06-17

许可协议

评论