概述

校验比如

大家平时编码中常常涉及参数的校验,关于一个用户注册的办法来说会校验用户名暗码信息:

public class UserController {
    public ResponseEntity<String> registerUser(String username, String password) {
        if (username == null || username.isEmpty()) {
            return ResponseEntity.badRequest().body("用户名不能为空");
        }
        if (password == null || password.isEmpty()) {
            return ResponseEntity.badRequest().body("暗码不能为空");
        }
        if (password.length() < 6) {
            return ResponseEntity.badRequest().body("暗码长度至少为6位");
        }
        // 处理用户注册逻辑
        return ResponseEntity.ok("用户注册成功");
    }
}

上述比如中需求手动编写参数校验逻辑的进程。尽管关于这个简单的示例而言,手动编写校验逻辑可能是可行的,可是关于杂乱的验证规矩和多个参数的情况,手动编写校验逻辑会变得冗长、难以保护和复用。

引进现代的校验结构如Spring Validation能够协助处理这些问题,提供更高效、统一和可保护的参数校验计划。

Bean Validation标准

  • JSR303/JSR-349/JSR-380: JSR303(Bean Validation)是一项标准,只提供标准不提供完成,规矩一些校验标准即校验注解,如@Null,@NotNull,@Pattern,位于javax.validation.constraints包下。JSR-349(Bean Validation 1.1)是其的晋级版别,增加了一些新特性。JSR-380(Bean Validation 2.0,JSR380标准 )对其标准进一步扩展和增强。
  • hibernate validationhibernate validation是对这个标准的完成,并增加了一些其他校验注解,如@Email,@Length,@Range等等
  • Spring validationspring validationhibernate validation进行了二次封装,在springmvc模块中增加了主动校验,并将校验信息封装进了特定的类中

Bean Validation的主页:beanvalidation.org
Bean Validation的参考完成:github.com/hibernate/h…

相关版别兼容性

Bean Validation Hibernate Validation JDK Spring Boot
1.1 5.4 + 6+ 1.5.x
2.0 6.0 + 8+ 2.0.x
3.0 7.0 + 9+ 2.0.x

3.0后Bean Validation改名为Jakarta Bean Validation 3.0了。
假如你的项目版别是jdk1.8的,不要运用hibernate-validator 7.0的版别,它里边的依靠的jakarta.validation-api:3.0是需求jdk1.9的部分支撑的。

Spring Validation注解

Spring Validation建立在Java Bean Validation(JSR 380)的基础上,为开发人员提供了一组注解和工具,用于界说和履行数据验证规矩。它答应开发人员在运用程序中界说验证规矩,并运用这些规矩来验证输入数据、恳求参数、范畴目标等。

@Validated:能够用在类型、办法和办法参数上。可是不能用在成员特点(字段)上

@Valid:能够用在办法、构造函数、办法参数和成员特点(字段)上

常用注解标签如下:

标签 阐明
@Null 约束只能为null
@NotNull 约束有必要不为null
@AssertFalse 约束有必要为false
@AssertTrue 约束有必要为true
@DecimalMax(value) 约束有必要为一个不大于指定值的数字
@DecimalMin(value) 约束有必要为一个不小于指定值的数字
@Digits(integer,fraction) 约束有必要为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 约束有必要是一个将来的日期
@Max(value) 约束有必要为一个不大于指定值的数字
@Min(value) 约束有必要为一个不小于指定值的数字
@Past 约束有必要是一个过去的日期
@Pattern(value) 约束有必要契合指定的正则表达式
@Size(max,min) 约束字符长度有必要在min到max之间
@Past 验证注解的元素值(日期类型)比当时时刻早
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、调集巨细不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只运用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也能够经过正则表达式和flag指定自界说的email格局

hibernate-validator 校验Java Bean

  • pom引进hibernate-validator
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.2.0.Final</version>
</dependency>
  • 创立一个Java Bean,咱们校验一下用户名跟年纪
public class User {
    @NotBlank(message = "用户名不能为空")
    private String username;
    @Min(value = 18, message = "年纪不能小于18岁")
    private int age;
    // 构造函数、Getter 和 Setter 办法
}
  • 履行校验
public class ValidatorTest {
    public static void main(String[] args) {
        // 创立校验器
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        // 创立用户目标
        User user = new User();
        user.setUsername("");
        user.setAge(16);
        // 履行校验
        Set<ConstraintViolation<User>> violations = validator.validate(user);
        // 处理校验成果
        if (!violations.isEmpty()) {
            for (ConstraintViolation<User> violation : violations) {
                System.out.println(violation.getMessage());
            }
        } else {
            System.out.println("校验经过");
        }
    }
}

用Spring Validation提高生产力

Spring Validation引进

增加pom依靠

Spring Validation校验包被独立成了一个starter组件,引进如下依靠:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

假如是spring-boot-starter-web不必引进了,spring-boot-starter-web 集成了spring-boot-starter-validation,默许能够不加spring-boot-starter-validation,它一起也集成了hibernate-validator

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

hibernate Validator校验器

这儿咱们界说hibernate校验器用于校验参数

@Configuration
@EnableAutoConfiguration
public class HibernateValidatorConfiguration {
	@Bean
	public MethodValidationPostProcessor methodValidationPostProcessor() {
		MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
		processor.setValidator(validator());
		processor.setProxyTargetClass(true);
		return processor;
	}
	@Bean
	public Validator validator() {
		return Validation
				.byProvider(HibernateValidator.class)
				.configure()
				.addProperty("hibernate.validator.fail_fast", "true")
				.buildValidatorFactory()
				.getValidator();
	}
}

大局反常处理

每个Controller办法中都假如都写一遍对校验成果信息的处理,运用起来仍是很繁琐。能够经过大局反常处理的方式统一处理校验反常。

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler(value = ConstraintViolationException.class)
	@ResponseBody
	public ApiResult defaultInsuranceExceptionHandler(ConstraintViolationException e) {
		log.error("校验过错:{}", e.getMessage());
		return ApiResult.error("error", e.getMessage());
	}
	@ExceptionHandler(value = MissingServletRequestParameterException.class)
	@ResponseBody
	public ApiResult handleMissingServletRequestParameter(MissingServletRequestParameterException ex) {
		String error = ex.getParameterName() + " 参数为空";
		return ApiResult.error("error", error);
	}
}

Controller接口Bean校验

首要,假定咱们有一个用户注册的恳求目标 UserRegistrationRequest,其间包括用户名和暗码字段。

@Data
public class UserRegistrationRequest {
    @NotBlank(message = "用户名不能为空")
    private String username;
    @NotBlank(message = "暗码不能为空")
    @Size(min = 6, message = "暗码长度至少为6位")
    private String password;
}

咱们运用了两个注解进行参数校验:

  • @NotBlank:该注解用于验证字段不能为空或空格,并能够经过 message 特点指定验证失利时的过错消息。
  • @Size:该注解用于验证字段的长度,咱们指定了暗码的最小长度为6,并经过 message 特点界说了验证失利时的过错消息。

接下来咱们界说一个Controller接口

@RestController
public class UserController {
    @PostMapping("/register")
    public ResponseEntity<String> registerUser(@Valid @RequestBody UserRegistrationRequest request) {
        // 处理用户注册逻辑
        return ResponseEntity.ok("用户注册成功");
    }
}

@RequestParam 参数校验

首要需求将MethodValidationPostProcessor设置成cglib代理

processor.setProxyTargetClass(true);

界说一个接口,咱们需求对ID进行校验,1<=id<=400

@Validated
public interface ValidClient {
	@GetMapping("/valid")
	String queryById(@RequestParam("id") @Min(1) @Max(400) Integer id);
}

controller完成

@RestController
public class ValidController implements ValidClient {
	@Override
	public String queryById(Integer id) {
		return "id:" + id;
	}
}

分组校验

仍是看上面那个Controller接口校验的比如,假如咱们注册的时分需求校验用户名和暗码,重置暗码的时分只校验暗码该怎么校验呢?这个时分就用到了分组校验了。

  • 首要,咱们需求界说一个新的分组,用于更新场景中的验证,例如UpdateGroup
    public interface UpdateGroup {
    }
    
  • 接下来,咱们需求在UserRegistrationRequest类的username字段上运用@Validated注解,并经过groups特点指定要运用的验证分组。
    public class UserRegistrationRequest {
        @NotBlank(message = "用户名不能为空", groups = {RegistrationGroup.class})
        private String username;
        @NotBlank(message = "暗码不能为空")
        @Size(min = 6, message = "暗码长度至少为6位")
        private String password;
        // 其他字段和办法
    }
    

在上述示例中,咱们将@Validated注解运用到username字段,并经过groups特点指定了RegistrationGroup.class,这意味着在注册场景中会进行校验。

  • 咱们能够运用@Validated注解来指定不要运用任何验证分组。
    @RestController
    public class UserController {
        @PostMapping("/register")
        public ResponseEntity<String> registerUser(@Validated(RegistrationGroup.class) @RequestBody UserRegistrationRequest request) {
            // 处理用户注册逻辑
            return ResponseEntity.ok("用户注册成功");
        }
        @PostMapping("/update")
        public ResponseEntity<String> updateUser(@Validated(UpdateGroup.class) @RequestBody UserRegistrationRequest request) {
            // 处理用户更新逻辑
            return ResponseEntity.ok("用户更新成功");
        }
    }
    

在上述示例中,咱们在updateUser办法中运用@Validated(UpdateGroup.class)来指定在更新场景中只履行UpdateGroup分组的验证规矩。因为username字段上没有指定groups特点,所以在更新场景中将不会对username字段进行校验。

自界说注解

Spring 的 validation 为咱们提供了许多特性,简直能够满意日常开发中绝大多数参数校验场景了。可是,一个好的结构一定是方便扩展的。有了扩展才能,就能应对更多杂乱的事务场景,下面咱们自界说一个日期格局校验的注解

界说注解接口

@Documented
@Constraint(validatedBy = DateFormatValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DateFormat {
	//默许过错消息
	String message() default "时刻格局过错";
	//分组
	Class<?>[] groups() default {};
	//默许日期格局
	String formatter() default "yyyy-MM-dd";
	//负载
	Class<? extends Payload>[] payload() default {};
	@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	@interface List {
		DateFormat[] value();
	}
}

界说校验器

public class DateFormatValidator implements ConstraintValidator<DateFormat, String> {
	protected String dateFormatter;
	@Override
	public void initialize(DateFormat constraintAnnotation) {
		this.dateFormatter = constraintAnnotation.formatter();
	}
	@Override
	public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
		if (StringUtils.isNotBlank(value)) {
			try {
				DateUtils.parseDate(value, dateFormatter);
			} catch (Exception e) {
				return false;
			}
			return true;
		}
		return false;
	}
}

自界说校验注解运用起来和官方注解没有差异,在需求的字段上增加相应注解即可。

public class RequestParam {
    @DateFormat(message = "日期输入过错")
    private String beginDate;
    // 其他字段和办法
}