对前台传来的参数,一般需要进行检验它的值是符合预期。如果使用if()else{},当参数很多时,代码几乎没有可维护性。可以借助hibernate校验工具方便实现这一功能。
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
或者
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
注意第二种依赖的groupId不是org.hibernate,因为这个groupId已经废弃,移至org.hibernate.validator。
校验@RequestParam参数
- 在Controller类上添加注解@Validated注解
- 在参数前加上校验注解
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.List;
@RestController
@Validated
public class ValidController {
@RequestMapping(method = RequestMethod.GET, value = "valid1")
public String valid1(@Length(min = 3,max = 10, message = "长度在3到10之间") @RequestParam String name,
@RequestParam @Max(value = 99, message = "不能大于99岁") Integer age) {
System.out.println(name);
return "ok";
}
}
校验@RequestBody参数
Stu实体类
class Stu{
@NotNull(message = "名字不能为空")
private String name;
@Range(min = 18, max = 30, message = "年龄在18到30之间")
private int age;
// getter and setter...
}
Controller接口
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.List;
@RestController
@Validated
public class ValidController {
@RequestMapping(method = RequestMethod.POST, value = "valid2")
public String valid2(@Valid @RequestBody Stu stu) {
return "ok";
}
}
注意在实体类参数前加的@Valid注解。
嵌套校验
如果实体类中还有实体类,按照上述方法添加@Valid是不会检查内部实体类的约束的。要使内部实体类的约束也生效,可以这样。
在Stu实体类中加入Hobby。
class Hobby {
@NotNull
@NotEmpty
private String name;
@Range(min = 0, max = 10)
private double price;
// getter and setter...
}
class Stu{
@NotNull(message = "名字不能为空")
private String name;
@Range(min = 18, max = 30, message = "年龄在18到30之间")
private int age;
@NotEmpty @Valid
List<Hobby> hobbies;
// getter and setter...
}
统一处理异常
当传入的参数不满足约束条件时,会抛出异常,可以对这些异常做统一处理。
对@RequestBody参数的校验失败会抛出MethodArgumentNotValidException。
对@RequestParam参数校验失败会抛出ConstraintViolationException,所以两种异常都要捕获。
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
@ControllerAdvice
public class MyAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
@ResponseBody
public String handlerException(MethodArgumentNotValidException ex) {
final List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
if (allErrors.size() > 0) {
final StringBuilder stringBuilder = new StringBuilder();
allErrors.forEach(error -> {
stringBuilder.append(error.getDefaultMessage()).append(",");
});
return stringBuilder.toString();
}
return "没有错误呀";
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
public String handlConstraintViolationException(ConstraintViolationException ex) {
final Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
if (constraintViolations == null || constraintViolations.size() == 0) {
return "没有异常呀";
}
final StringBuilder stringBuilder = new StringBuilder();
constraintViolations.forEach(e -> {
stringBuilder.append(e.getMessage()).append(",");
});
return stringBuilder.toString();
}
}
测试
校验@RequestParam参数
校验@RequestBody参数
1.校验外层实体类
2.校验内部实体类
普通方法校验参数
如果我要校验的参数不在Controller里,而在一个service里,配置流程和@RequestParam参数的校验一致。也是先在类上加@Validated,再用校验注解修饰参数。有三点要注意
- 校验复杂参数用@Valid
- 注意接口与实现类中的注解的一致性
- 如果校验不通过,也是抛出ConstraintViolationException异常
//service 接口
public interface HelloService {
String sayHello(@Valid Stu stu);//注意这里的注解要与实现类一致
}
//service 实现类
@Validated
@Service
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(@Valid Stu stu) {
return stu.getName() + " sayhello";
}
}
快速失败
默认情况下会对所有参数进行校验,就像上文对@RequestParam参数的校验,一旦校验不通过,会把所有提示信息返回。如果我们希望只要校验不通过就立马返回,不再进行其他参数校验,可以配置failFast属性为true。
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}