zhaoyu@home:~$

spring参数校验

Spring的参数校验遵从JSR303标准,spring为了给开发者提供便捷,对hibernate validation进行了二次封装,显示校验validated bean时, 你可以使用spring validation或者hibernate validation。一般在使用时,这两套API都是混着用。

Spring Boot引入依赖

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

bean 的校验

定义对象如下:

import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class DTO {
    @NotNull(message ="id需要是20位以内的数字")
    private Long id;
    @NotBlank(message ="真实姓名不能为空")
    @Length(max=20,message = "真实姓名最大长度为20")
    private String name;
    @NotBlank(message ="证件号码不能为空")
    @Length(max=50,message = "证件号码最大长度为50")
    private String identify;
}

Controller中的引用

public ResultMsg authentication(@Valid DTO dto,BindingResult bindingResult)

其中\@Valid 和BindingResult是一对一的,\@Valid 验证的结果会存放在BindingResult中。接下来对BindingResult的处理如下:

@RequestMapping("/foo")
public String foo(@Validated Foo foo, BindingResult bindingResult) {
    if(bindingResult.hasErrors()){
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            //...
        }
        return "fail";
    }
    return "success";
}

在获得验证的信息后,如果错误,则向客户端返回验证信息,无错误,继续处理。

方法中参数的校验

注入MethodValidationPostProcessor Bean

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
    return new MethodValidationPostProcessor();
}

在需要方法验证的类上加注解 \@Validated

@Controller
@Validated
@RequestMapping("valid")
public class ValidController {
    @RequestMapping("/check")
    @ResponseBody
    public String check(@Min(value = 2,message = "age必须大于2") int age) {
        return "" + age;
    }
}

方法验证会抛出ConstraintViolationException 异常,我们需要对ConstraintViolationException 做出统一处理,异常处理器定义如下:

异常处理器

@RestControllerAdvice
public class BindingExceptionHandler {
    /**
     * 用于处理入参绑定异常ConstraintViolationException
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handler(ConstraintViolationException e) {
        StringBuilder strBuilder = new StringBuilder();
        e.getConstraintViolations().stream().forEach(
                violation -> {
                    strBuilder.append(violation.getMessage()+"\n");
                }
        );
        return strBuilder.toString();
    }
}

如果使用了\@Validated,那么Bean的校验也会抛出异常而不是之前的封装在BindingResult中。

校验规则

JSR提供的校验注解:         
@Null   被注释的元素必须为 null    
@NotNull    被注释的元素必须不为 null    
@AssertTrue     被注释的元素必须为 true    
@AssertFalse    被注释的元素必须为 false    
@Min(value)     被注释的元素必须是一个数字,其值必须大于等于指定的最小值    
@Max(value)     被注释的元素必须是一个数字,其值必须小于等于指定的最大值    
@DecimalMin(value)  被注释的元素必须是一个数字,其值必须大于等于指定的最小值    
@DecimalMax(value)  被注释的元素必须是一个数字,其值必须小于等于指定的最大值    
@Size(max=, min=)   被注释的元素的大小必须在指定的范围内    
@Digits (integer, fraction)     被注释的元素必须是一个数字,其值必须在可接受的范围内    
@Past   被注释的元素必须是一个过去的日期    
@Future     被注释的元素必须是一个将来的日期    
@Pattern(regex=,flag=)  被注释的元素必须符合指定的正则表达式    

Hibernate Validator提供的校验注解:  
@NotBlank(message =)   验证字符串非null,且长度必须大于0    
@Email  被注释的元素必须是电子邮箱地址    
@Length(min=,max=)  被注释的字符串的大小必须在指定的范围内    
@NotEmpty   被注释的字符串的必须非空    
@Range(min=,max=,message=)  被注释的元素必须在合适的范围内

分组校验

有的时候,我们对一个实体类需要有多中验证方式,在不同的情况下使用不同验证方式,比如说对于一个实体类来的id来说,保存的时候 是不需要的,对于更新时是必须的,可以如下配置:

public class UserModel {
 	@NotNull(message = "{id.empty}", groups = { First.class })	
 	private int id; 
    @NotNull(message = "{username.empty}", groups = { First.class, Second.class })
	private String username; 	
    @NotNull(message = "{content.empty}", groups = { First.class, Second.class })	
    private String content; 	
    
    public interface First {} 
    public interface Second {}
}

在controller中的调用如下:

@RequestMapping(value = "/save.action", method = RequestMethod.POST)
public String save(@Validated( { Second.class }) UserModel userModel, BindingResult result) { …… }
@RequestMapping(value = "/update.action", method = RequestMethod.POST)
public String update(@Validated( { First.class, Second.class }) UserModel user, BindingResult result) { ……	}

分组只能使用@Validated。

组序列

默认情况下,不同组别的约束验证是无序的,然而在某些情况下,约束验证的顺序却很重要,如下面两个例子:(1)第二个组中的约束 验证依赖于一个稳定状态来运行,而这个稳定状态是由第一个组来进行验证的。(2)某个组的验证比较耗时,CPU 和内存的使用率相对 比较大,最优的选择是将其放在最后进行验证。因此,在进行组验证的时候尚需提供一种有序的验证方式,这就提出了组序列的概念。

在使用组序列验证的时候,如果序列前边的组验证失败,则后面的组将不再给予验证。

public interface GroupA {}
public interface GroupB {} 
@GroupSequence( { Default.class, GroupA.class, GroupB.class })
public interface Group {} 
public class User {	
    @NotEmpty(message = "firstname may be empty")	
    private String firstname; 	
    @NotEmpty(message = "middlename may be empty", groups = Default.class)	
    private String middlename; 	
    @NotEmpty(message = "lastname may be empty", groups = GroupA.class)	
    private String lastname; 	
    @NotEmpty(message = "country may be empty", groups = GroupB.class)	
    private String country;
}

controller中的验证如下:

@RequestMapping(value = "/update.action", method = RequestMethod.POST)
public String register(@Validated(Group.class) User user, BindingResult result) 
{……}

验证多个对象

有时候我们需要传递多个验证的对象,通过如下形式验证。

@RequestMapping("/validate/multi")
public String multi(@Valid @ModelAttribute("a") A a, BindingResult aErrors, @Valid @ModelAttribute("b") B b, 
    BindingResult bErrors) { 	
    if (aErrors.hasErrors()) {……}	
    if (bErrors.hasErrors()) {……}
}