springboot中使用自定义参数验证器
此文为本人对自定义参数验证器学习的总结,不涉及分组验证等其他知识。若有任何错误和不足之处,望指出。
自定义参数验证器一般只在引入的验证器不能完成任务时,才需要自定义参数验证器,比如多个字段需要联合验证时。
1. 新建springboot web项目
idea中选择Spring Initializr工具
添加web支持:
从spring-boot-starter-web的依赖中可以看到,已经包含了验证器的依赖:
所以不需要再另外添加验证器的依赖。
2. 设定需求:
有一个登录接口,会传入User对象的json字符串,现在要求,如果phone字段不为null,则要求age字段的值大于18.此时,不能直接在age字段上直接添加@Min注解,设置最小值是19, 因为对这个字段的最小值的要求,是有前提的。这时,就需要自定义验证器。
public class User {
private String phone;
@NotBlank
private String name;
@NotNull(message = "age不能为null")
private Integer age;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
3. 实现验证器
验证器注解:
@Target({ ElementType.TYPE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = CheckAgeImpl.class)
public @interface CheckAge {
String message() default "当phone不为空的时候,age字段必须大于18";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
-
@Target({ ElementType.TYPE})
,这个注解只能在类上使用,虽然要校验的是age字段,但是这个注解不能放在age字段上,因为如果放在age字段上,验证器到时候只能接收到age字段的值,但这个需求是有前提的,所以需要接收整个User对象,所以这个注解,要放在User类上,所以这里的Target,要设置为ElementType.TYPE
。 -
@Retention(RUNTIME)
:这个注解要在运行时起作用。 -
@Constraint(validatedBy = CheckAgeImpl.class)
: 这个注解设置了验证器功能的实现类。 -
message
,groups
,payload
这个3个属性必须的,即便不用,也不能省略。
验证器的实现:
public class CheckAgeImpl implements ConstraintValidator<CheckAge, User> {
@Override
public void initialize(CheckAge constraintAnnotation) {
}
@Override
public boolean isValid(User value, ConstraintValidatorContext context) {
if(!StringUtils.isEmpty(value.getPhone())){
if(value.getAge() == null){
return false;
}
if(value.getAge() <= 18){
return false;
}
}
return true;
}
}
自定义验证器必须实现ConstraintValidator<A extends Annotation, T>
接口。其中第一个泛型参数A是该验证器是为哪个注解实现验证功能,
可以看到这里要求我们的注解必须实现了Annotation接口,但是我们定义注解的时候,并没有实现Annotation接口,实际上编译器干了这个事,它会自动地让我们定义的注解实现Annotation接口。使用javap反编译即可看到:
javap -c CheckAge.class:
public interface com.woslx.springbootvaild.CheckAge extends java.lang.annotation.Annotation {
public abstract java.lang.String message();
public abstract java.lang.Class<?>[] groups();
public abstract java.lang.Class<? extends javax.validation.Payload>[] payload();
}
从这里也说明,注解,其实本质上是接口,这也就是为什么注解的属性,是以方法的形式出现。
第二个参数T是被校验的对象的类型。这里虽然是验证age字段,但是因为有phone字段不为null的前提,实际上,这是对User对象的校验,而不是单纯对age字段的校验。
这个接口中的isValid方法,返回true表示验证通过,false表示验证未通过。
4. 使用验证器
把这个验证器注解放到User类上:
@CheckAge
public class User {
private String phone;
@NotBlank
private String name;
@NotNull(message = "age不能为null")
private Integer age;
...省略getter/setter方法
Controller类:
@RestController
public class LoginController {
@RequestMapping(value = "/login", consumes = "application/json")
public String login(@Valid @RequestBody User user, BindingResult bindingResult){
if(bindingResult.hasErrors()){
List<ObjectError> allErrors = bindingResult.getAllErrors();
StringBuilder sb = new StringBuilder();
sb.append("error:");
for(ObjectError error: allErrors){
sb.append("\n").append(error.getDefaultMessage());
}
return sb.toString();
}
return "success";
}
}
5. 测试
测试1:不设置phone字段:
public class TestLogin {
public static final String url = "http://127.0.0.1:8080/";
@Test
public void test2() throws Exception{
String urlTemp = url+"login";
User user = new User();
user.setName("Tom");
user.setAge(10);
System.out.println(JSON.toJSONString(user));
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(user));
Request request = new Request.Builder()
.url(urlTemp)
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("Cache-Control", "no-cache")
.build();
Response response = client.newCall(request).execute();
String respMessage = response.body().string();
System.out.println(respMessage);
}
}
输出:
{“age”:10,“name”:“Tom”}
success
测试2: 设置phone字段,但是不设置age字段:
User user = new User();
user.setPhone("13609876543");
user.setName("Tom");
{“name”:“Tom”,“phone”:“13609876543”}
error:
age不能为null
当phone不为空的时候,age字段必须大于18
测试3: 设置phone字段,但是age字段小于18
User user = new User();
user.setPhone("13609876543");
user.setName("Tom");
user.setAge(10);
输出:
{“age”:10,“name”:“Tom”,“phone”:“13609876543”}
error:
当phone不为空的时候,age字段必须大于18
- 源码地址
源码已经放在github上: https://github.com/hysgit/springbootvaild