准备工作
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
新建参数对象:TestValidateDTO
@Data
@ToString
public class TestValidateDTO {
@NotBlank
private String name;
@Email
private String email;
@Pattern(regexp = "0?(13|14|15|17|18|19)[0-9]{9}")
private String phone;
@NotBlank
private String word;
}
调用关系
@RestController
@RequestMapping("/validate")
public class TestValidateWeb {
@Autowired
private TestValidateService tvs;
@PostMapping("/test1")
public String test1(@Validated @RequestBody TestValidateDTO dto){
return tvs.test1(dto);
}
}
@Service
public class TestValidateService {
public String test1(TestValidateDTO dto){
return dto.toString();
}
}
测试效果
正常数据验证:
修改name,email,phone,word的内容,故意不符合规则:
虽然有报错但是报错提示400,很不人性化,继续修改报错提示。
修改抛出异常提示
@RestControllerAdvice
public class ResponseException {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public String validateException(MethodArgumentNotValidException exc){
return "出现问题:"+exc.getBindingResult().getFieldError().getDefaultMessage();
}
@ExceptionHandler(value = Exception.class)
public String exception(Exception exc){
return "出现问题:"+exc.getMessage();
}
}
@Data
@ToString
public class TestValidateDTO {
@NotBlank(message = "姓名不能为空")
private String name;
@Email(message = "不是符合格式的邮箱")
private String email;
@Pattern(regexp = "0?(13|14|15|17|18|19)[0-9]{9}", message = "手机号码不符合格式")
private String phone;
@NotBlank(message = "说句话")
private String word;
}
在参数对象的注解上加上message。
符合预期效果。
分组校验
同样的一个参数对象,可能两个接口对参数对象的校验规则是不一样的,可以使用groups实现在同一个参数对象上不同接口有不同的校验规则。
新建一个类,里面有两个内部接口,新建内部接口是因为**@Validated**(**value **= {})中value只接受接口类型
public class TestValidateGroups {
public interface test1{};
public interface test2 {};
}
接口参数中加上**@Validated**(**value **= {TestValidateGroups.test2.class}),不同接口加上不同内部接口。
@RestController
@RequestMapping("/validate")
@Validated
public class TestValidateWeb {
@Autowired
private TestValidateService tvs;
@PostMapping("/test1")
public String test1(@Validated(value = {TestValidateGroups.test1.class}) @RequestBody TestValidateDTO dto) {
return tvs.test1(dto);
}
@PostMapping("/test2")
public String test2(@Validated(value = {TestValidateGroups.test2.class}) @RequestBody TestValidateDTO dto) {
return tvs.test1(dto);
}
}
参数对象中name和word指定内部接口,和接口层的关系对应上。
@Data
@ToString
public class TestValidateDTO {
@NotBlank(message = "姓名不能为空", groups = {TestValidateGroups.test1.class})
private String name;
@Email(message = "不是符合格式的邮箱")
private String email;
@Pattern(regexp = "0?(13|14|15|17|18|19)[0-9]{9}", message = "手机号码不符合格式")
private String phone;
@NotBlank(message = "说句话", groups = {TestValidateGroups.test2.class})
private String word;
}
调用这两个接口,参数对象都一样:
可以看到参数对象都是一样的,但是提示是不一样的,说明校验规则是做了区分的。
❗注意,如果这个时候我在email属性上加了如下的规则
@NotBlank(message = “邮箱不能为空”, groups = {TestValidateGroups.test1.class, TestValidateGroups.test2.class})
@Email(message = “不是符合格式的邮箱”)
private String email;
因为**@Email没有指定分组,所以不管如何调用都不会校验是否是邮箱,@NotBlank因为加了指定分组所以才会校验是否为空。**
出现这个问题,是因为在test1接口指定了TestValidateGroups.test1.class分组,如果在test1接口中将指定分组删掉:
@RestController
@RequestMapping("/validate")
@Validated
public class TestValidateWeb {
@Autowired
private TestValidateService tvs;
@PostMapping("/test1")
public String test1(@Validated @RequestBody TestValidateDTO dto) {
return tvs.test1(dto);
}
@PostMapping("/test2")
public String test2(@Validated(value = {TestValidateGroups.test2.class}) @RequestBody TestValidateDTO dto) {
return tvs.test1(dto);
}
}
这个时候也只会校验在参数对象中没有指定分组的属性。改变phone也是会校验的。
自定义注解校验规则
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER,})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = TestAnnotationValidateImpl.class)
@Repeatable(TestAnnotation.List.class)
public @interface TestAnnotation {
String message() default "字段不符合规范";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER,})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
TestAnnotation[] value();
}
}
这里出现的@Target注解指定的用途需要一致,个数一样,值一样,不然会提示错误。
@Component
public class TestAnnotationValidateImpl implements ConstraintValidator<TestAnnotation, TestValidateDTO> {
@Override
public void initialize(TestAnnotation constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(TestValidateDTO testValidateDTO, ConstraintValidatorContext constraintValidatorContext) {
if (testValidateDTO == null) {
return true;
}
if (StringUtils.isBlank(testValidateDTO.getWord())) {
return false;
}
// 返回false就会抛出message
return !testValidateDTO.getWord().contains("c") && !testValidateDTO.getWord().contains("c") &&
!testValidateDTO.getWord().contains("cnm");
}
}
@RestController
@RequestMapping("/validate")
@Validated
public class TestValidateWeb {
@Autowired
private TestValidateService tvs;
@PostMapping("/test1")
public String test1(@Validated @RequestBody TestValidateDTO dto) {
return tvs.test1(dto);
}
@PostMapping("/test2")
public String test2(@Validated(value = {TestValidateGroups.test2.class}) @TestAnnotation(message = "cnm,别说脏话啊") @RequestBody TestValidateDTO dto) {
return tvs.test1(dto);
}
}
在test2接口上加上TestAnnotation注释,因为我将TestAnnotation注解设置为可以用在参数上。
效果符合预期。