SpringBoot如何⾃定义请求参数校验
⽬录
⼀、Bean Validation基本概念
⼆、基本⽤法
三、⾃定义校验
3.1 ⾃定义注解
3.2 ⾃定义Validator
3.3 以编程的⽅式校验(⼿动)
3.4 定义分组校验
3.5 定制返回码和消息
3.6 更加细致的返回码和消息
四、⼩结
最近在⼯作中遇到写⼀些API,这些API的请求参数⾮常多,嵌套也⾮常复杂,如果参数的校验代码全部都⼿动去实现,写起来真的⾮常痛苦。
正好Spring轮⼦⾥⾯有⼀个Validation,这⾥记录⼀下怎么使⽤,以及怎么⾃定义它的返回结果。
⼀、Bean Validation基本概念
Bean Validation是Java中的⼀项标准,它通过⼀些注解表达了对实体的限制规则。通过提出了⼀些API和扩展性的规范,这个规范是没有提供具体实现的,希望能够Constrain once, validate everywhere。现在它已经发展到了2.0,兼容Java8。hibernate validation实现了Bean Validation标准,⾥⾯还增加了⼀些注解,在程序中引⼊它我们就可以直接使⽤。
Spring MVC也⽀持Bean Validation,它对hibernate validation进⾏了⼆次封装,添加了⾃动校验,并将校验信息封装进了特定的BindingResult类中,在SpringBoot中我们可以添加implementation(‘org.springframework.boot:spring-boot-starter-validation')引⼊这个库,实现对bean的校验功能。
⼆、基本⽤法
gradle dependencies如下:
dependencies {
implementation('org.springframework.boot:spring-boot-starter-validation')
implementation('org.springframework.boot:spring-boot-starter-web')
}
定义⼀个⽰例的Bean,例如下⾯的User.java。
public class User {
@NotBlank
@Size(max=10)
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
在name属性上,添加@NotBlank和@Size(max=10)的注解,表⽰User对象的name属性不能为字符串且长度不能超过10个字符。
然后我们暂时不添加任何多余的代码,直接写⼀个UserController对外提供⼀个RESTful的GET接⼝,注意接⼝的参数⽤到了@Validated注解。
// UserController.java,省略其他代码
@RestController
public class UserController {
@RequestMapping(value = "/validation/get", method = RequestMethod.GET)
public ServiceResponse validateGet(@Validated User user) {
ServiceResponse serviceResponse = new ServiceResponse();
serviceResponse.setCode(0);
serviceResponse.setMessage("test");
return serviceResponse;
}
}spring mvc和boot区别
// ServiceResponse.java,简单包含了code、message字段返回结果。
public class ServiceResponse {
private int code;
private String message;
... 省略getter、setter ...
}
启动SpringBoot程序,发⼀个测试请求看⼀下:
此时已经可以实现参数的校验了,但是返回的结果不太友好,下⾯看⼀下怎么定制返回的消息。在定制返回结果前,先看下⼀下内置的校验注解有哪些,在这⾥我不⼀个个去贴了,写代码的时候根据需要进⼊到源码⾥⾯去看即可。
早期Spring版本中,都是在Controller的⽅法中添加Errors/BindingResult参数,由Spring注⼊Errors/BindingResult对象,再在Controller中⼿写校验逻辑实现校验。
新版本提供注解的⽅式(Controller上⾯bean加⼀个@Validated注解),将校验逻辑和Controller分离。
三、⾃定义校验
3.1 ⾃定义注解
显然除了⾃带的NotNull、NotBlank、Size等注解,实际业务上还会需要特定的校验规则。
假设我们有⼀个参数address,必须以Beijing开头,那我们可以定义⼀个注解和⼀个⾃定义的Validator。
// StartWithValidator.java
public class StartWithValidator implements ConstraintValidator<StartWithValidation, String> {
private String start;
@Override
public void initialize(StartWithValidation constraintAnnotation) {
start = constraintAnnotation.start();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (!StringUtils.isEmpty(value)) {
return value.startsWith(start);
}
return true;
}
}
// StartWithValidation.java
@Documented
@Constraint(validatedBy = StartWithValidator.class)
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface StartWithValidation {
String message() default "不是正确的性别取值范围";
String start() default "_";
Class[] groups() default {};
Class[] payload() default {};
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@interface List {
GenderValidation[] value();
}
}
然后在User.java中增加⼀个address属性,并给它加上上⾯这个⾃定义的注解,这⾥我们定义了⼀个可以传⼊start参数的注解,表⽰应该以什么开头。
@StartWithValidation(message = "Param 'address' must be start with 'Beijing'.", start = "Beijing")
private String address;
除了定义可以作⽤于属性的注解外,其实还可以定义作⽤于class的注解(@Target({TYPE})),⽤于校验class的实例。
3.2 ⾃定义Validator
第⼀步,实现⼀个Validator。(这种⽅法不需要我们的bean⾥⾯有任何注解之类的东西)
ample.validation.demo;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
@Component
public class UserValidator implements Validator {
@Override
public boolean supports(Class clazz) {
return User2.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
User2 p = (User2) target;
if (p.getId() == 0) {
}
}
}
第⼆步,修改Controller代码,注⼊上⾯的UserValidator实例,并给Controller的⽅法参数加上@Validated注解,即可完成和前⾯⾃定义注解⼀样的校验功能。
@RestController
public class UserController {
@Autowired
UserValidator validator;
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
@RequestMapping(value = "/user/post", method = RequestMethod.POST)
public ServiceResponse handValidatePost(@Validated @RequestBody User user) {
ServiceResponse serviceResponse = new ServiceResponse();
serviceResponse.setCode(0);
serviceResponse.setMessage("test");
return serviceResponse;
}
}
这个⽅法和⾃定义注解的区别在于不需要在Bean⾥⾯添加注解,并且可以更加灵活的把⼀个Bean⾥⾯所有的Field的校验代码都搬到⼀起,⽽不是每⼀个属性都去加注解,如果校验的属性⾮常多,且默认注解的能⼒⼜不够的话,这种⽅式也是不错的,可以避免⼤量的⾃定义注解。
3.3 以编程的⽅式校验(⼿动)
这种⽅式可以算是原始的Hibernate-Validation的⽅式。直接看代码,这⾥有⼀个⽐较不同的是,可以
使⽤Hibernate-Validation的Fail fast mode。因为前⾯的⽅式,都将所有的参数都验证完了,再把错误返回。有时我们希望遇到⼀个参数错误,就⽴即返回。
设置fast-fail为true可以达到这个⽬的。不过貌似不能再⽤@Validated注解⽅法参数了,⽽是要⽤ValidatorFactory创建Validator。
在实际开发中,不必每次都编写代码创建Validator,可以采⽤@Configuration的⽅式创建,然后再@Autowired注⼊到每个需要使⽤Validator的Controller当中。
@RestController
public class UserController {
...
@RequestMapping(value = "/validation/postStudent", method = RequestMethod.POST)
public ServiceResponse validatePostStudent(@RequestBody User user) {
// User参数前⾯没有@Validated注解了,User类⾥⾯那些注解还是保留着即可。
HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class).configure();
ValidatorFactory factory = configuration.failFast(true).buildValidatorFactory(); // fastFail
Validator validator = Validator();
Set<constraintviolation> set = validator.validate(user);
// 根据set的size,⼤于0时,抛异常。由于设置了failFast,这⾥set最多就⼀个元素
ServiceResponse serviceResponse = new ServiceResponse();
serviceResponse.setCode(0);
serviceResponse.setMessage("test");
return serviceResponse;
}
}
3.4 定义分组校验
有的时候,我们会有两个不同的接⼝,但是会使⽤到同⼀个Bean来作为VO(意思是两个接⼝的URI不同,但参数中都⽤到了同⼀个Bean)。
⽽在不同的接⼝上,对Bean的校验需求可能不⼀样,⽐如接⼝2需要校验studentId,⽽接⼝1不需要。那么此时就可以⽤到校验注解的分组groups。
// User.java
public class User {
... 省略其他属性
// 指明在groups={Student.class}时才需要校验studentId
@NotNull(groups = {Student.class}, message = "Param 'studentId' must not be null.")
private Long studentId;
// 增加Student interface
public interface Student {
}
}
// UserController.java,增加了⼀个/getStudent接⼝
@RestController
public class UserController {
@RequestMapping(value = "/validation/get", method = RequestMethod.GET)
public ServiceResponse validateGet(@Validated User user) {
ServiceResponse serviceResponse = new ServiceResponse();
serviceResponse.setCode(200);
serviceResponse.setMessage("test");
return serviceResponse;
}
@RequestMapping(value = "/validation/getStudent", method = RequestMethod.GET)
public ServiceResponse validateGetStudent(@Validated({User.Student.class}) User user) {
ServiceResponse serviceResponse = new ServiceResponse();
serviceResponse.setCode(0);
serviceResponse.setMessage("test");
return serviceResponse;
}
}
到这⾥,也可以带⼀嘴Valid和Validated注解的区别,其代码注释写着后者是对前者的⼀个扩展,⽀持了group分组的功能。
3.5 定制返回码和消息
第⼆节中定义了⼀个ServiceResponse,其实作为⼀个开放的API,不论⽤户传⼊任何参数,返回的结果都应该是预先定义好的格式,并且可以写明在接⼝⽂档中,即使发⽣了校验失败,应该返回⼀个包含错误码code(发⽣错误时⼀般⼤于0)和message字段。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论