Spring MVC parameter check (JSR303) Request

Source:  so write parameter check (validator) will not be discouraging ~

 

 

  Parity information is very painful encounter a large number of parameters to check, but also thrown in the business or continue to return an exception in the code is quite lengthy, full of if-else this check codes, today we have learn javax.validation annotation type parameter calibration of spring.

 

Why validator


1:javax.validationThe series of annotations can help us complete parameter check, replacing the cumbersome serial check

Otherwise, our code like this:

//  http://localhost:8080/api/user/save/serial

    /**
     * Serial go check
     *
     * @param userVO
     * @return
     */
    @PostMapping("/save/serial")
    public Object save(@RequestBody UserVO userVO) {
        String mobile = userVO.getMobile();

        // manual ~ calibration parameters individually writing 
        IF (StringUtils.isBlank (Mobile)) {
             return RspDTO.paramFail ( "Mobile: mobile phone number can not be empty" );
        } The else  IF (! Pattern.matches ( "^ [. 1] [3,4,5,6,7,8,9] [0-9] {} $. 9" , Mobile)) {
             return RspDTO.paramFail ( " mobile: phone number format is not right " );
        }

        // throw a custom exception wording like ~ 
        IF (StringUtils.isBlank (userVO.getUsername ())) {
             the throw  new new BizException (Constant.PARAM_FAIL_CODE, "user name can not be blank" );
        }

        // such as writing a map to return 
        IF (StringUtils.isBlank (userVO.getSex ())) {
            Map<String, Object> result = new HashMap<>(5);
            result.put("code", Constant.PARAM_FAIL_CODE);
            result.put ( "msg", "sex can not be empty" );
             return the Result;
        }
        // ......... all kinds of writing ... 
        userService.save (userVO);
         return RspDTO.success ();
    }

 

2: What isjavax.validation

  JSR303 is a set of standard JavaBean parameter check, which defines many common check notes, we can directly attribute these annotations added to the above (for programming notes era) our JavaBean, you can check in when needed a checksum in SpringBoot already contains the starter-web, and then rely on other items may be referenced, and adjust the version is:

      <!--jsr 303-->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.1.0.Final</version>
        </dependency>
        <!-- hibernate validator-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.2.0.Final</version>
        </dependency>

 

3: explanatory notes

  1. @ NotNull: it can not be null, but can be empty ( "", "", "")

  2. @ NotEmpty: it is not null, and the length must be greater than 0 ( "", "")

  3. @ NotBlank: after only act on String, can not be null, and call the trim (), the length must be greater than 0 ( "test") that is: there must be an actual character

Verify comment Verification of data types Explanation
@AssertFalse Boolean,boolean Verify annotation element value is false
@AssertTrue Boolean,boolean Verify annotation element value is true
@NotNull Any type Verify the annotated element is not null
@Null Any type Verify the annotated element is null
@Min (value = value) BigDecimal, BigInteger, byte, short, int, long, etc. or any CharSequence Number (stored digital) subtype Verify annotation element value is greater than a value equal to the value specified @Min
@Max (value = value) The same requirements and @Min Verify annotation element value less than or equal to the specified value value @Max
@DecimalMin(value=值) The same requirements and @Min Verify annotation element value is greater than a specified value equal to the value @ DecimalMin
@DecimalMax(value=值) The same requirements and @Min Verify annotation element value less than or equal to the specified value value @ DecimalMax
@Digits (integer = integer bits, fraction = number of decimal places) The same requirements and @Min Integer-digit verification annotation element value and an upper limit of decimal places
@Size (min = lower limit, max = upper limit) String, Collection, Map, arrays, etc. Within min and max (comprising) the designated section, such as character length validation annotation element value, the set size
@Past java.util.Date, java.util.Calendar; Joda Time date type library Verify the annotated element values ​​(date type) earlier than the current time
@Future Like @Past requirements Verify annotation element value (date type) later than the current time
@NotBlank CharSequence subtype Verify annotation element value is not empty (not null, the space after the removal of the first length of 0), unlike @ NotEmpty, @ NotBlank applied only in the comparison string and removes the first blank string
@Length (min = lower limit, max = upper limit) CharSequence subtype Element value length validation and annotation in the range min max
@NotEmpty CharSequence subtype, Collection, Map, Array Verify annotation element value is not null and is not empty (the string length is not 0, set size is not 0)
@Range (min = Min, max = maximum value) BigDecimal, BigInteger, CharSequence, byte, short, int, long atom type and package type, etc. Verify annotation element value between the minimum and maximum values
@Email (regexp = regular expressions, flag = mode flag) CharSequence subtype (e.g., String) Element value is verified annotation Email, can also specify a custom email format flag and by regexp
@Pattern (regexp = regular expressions, flag = mode flag) String, CharSequence any subtype Verify annotation element value specified regular expression matching
@Valid Any type of non-atomic 指定递归验证关联的对象如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证

此处只列出Hibernate Validator提供的大部分验证约束注解,请参考hibernate validator官方文档了解其他验证约束注解和进行自定义的验证约束注解定义。

 

实战演练


  话不多说,直接走实践路线,同样使用的是SpringBoot的快速框架,详细代码见:github.com/leaJone/myb…

1. @Validated 声明要检查的参数

这里我们在控制器层进行注解声明

/**
     * 走参数校验注解
     *
     * @param userDTO
     * @return
     */
    @PostMapping("/save/valid")
    public RspDTO save(@RequestBody @Validated UserDTO userDTO) {
        userService.save(userDTO);
        return RspDTO.success();
    }

 

2. 对参数的字段进行注解标注

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

import javax.validation.constraints.*;
import java.io.Serializable;
import java.util.Date;

/**
 * @author LiJing
 * @ClassName: UserDTO
 * @Description: 用户传输对象
 * @date 2019/7/30 13:55
 */
@Data
public class UserDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    /*** 用户ID*/
    @NotNull(message = "用户id不能为空")
    private Long userId;
    
    /** 用户名*/
    @NotBlank(message = "用户名不能为空")
    @Length(max = 20, message = "用户名不能超过20个字符")
    @Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "用户昵称限制:最多20字符,包含文字、字母和数字")
    private String username;
    
    /** 手机号*/
    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误")
    private String mobile;

    /**性别*/
    private String sex;

    /** 邮箱*/
    @NotBlank(message = "联系邮箱不能为空")
    @Email(message = "邮箱格式不对")
    private String email;

    /** 密码*/
    private String password;

    /*** 创建时间 */
    @Future(message = "时间必须是将来时间")
    private Date createTime;

}

 

3. 在全局校验中增加校验异常

MethodArgumentNotValidException是springBoot中进行绑定参数校验时的异常,需要在springBoot中处理,其他需要 处理ConstraintViolationException异常进行处理.

  • 为了优雅一点,我们将参数异常,业务异常,统一做了一个全局异常,将控制层的异常包装到我们自定义的异常中
  • 为了优雅一点,我们还做了一个统一的结构体,将请求的code,和msg,data一起统一封装到结构体中,增加了代码的复用性
import com.boot.lea.mybot.dto.RspDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;


/**
 * @author LiJing
 * @ClassName: GlobalExceptionHandler
 * @Description: 全局异常处理器
 * @date 2019/7/30 13:57
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private static int DUPLICATE_KEY_CODE = 1001;
    private static int PARAM_FAIL_CODE = 1002;
    private static int VALIDATION_CODE = 1003;

    /**
     * 处理自定义异常
     */
    @ExceptionHandler(BizException.class)
    public RspDTO handleRRException(BizException e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(e.getCode(), e.getMessage());
    }

    /**
     * 方法参数校验
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public RspDTO handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(PARAM_FAIL_CODE, e.getBindingResult().getFieldError().getDefaultMessage());
    }

    /**
     * ValidationException
     */
    @ExceptionHandler(ValidationException.class)
    public RspDTO handleValidationException(ValidationException e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(VALIDATION_CODE, e.getCause().getMessage());
    }

    /**
     * ConstraintViolationException
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public RspDTO handleConstraintViolationException(ConstraintViolationException e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(PARAM_FAIL_CODE, e.getMessage());
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public RspDTO handlerNoFoundException(Exception e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(404, "路径不存在,请检查路径是否正确");
    }

    @ExceptionHandler(DuplicateKeyException.class)
    public RspDTO handleDuplicateKeyException(DuplicateKeyException e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(DUPLICATE_KEY_CODE, "数据重复,请检查后提交");
    }


    @ExceptionHandler(Exception.class)
    public RspDTO handleException(Exception e) {
        logger.error(e.getMessage(), e);
        return new RspDTO(500, "系统繁忙,请稍后再试");
    }
}

 

4. 测试

如下文:确实做到了参数校验时返回异常信息和对应的code,方便了我们不再繁琐的处理参数校验

 

在ValidationMessages.properties 就是校验的message,有着已经写好的默认的message,且是支持i18n的,大家可以阅读源码赏析

自定义参数注解

1. 比如我们来个 自定义身份证校验 注解

@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IdentityCardNumberValidator.class)
public @interface IdentityCardNumber {

    String message() default "身份证号码不合法";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

这个注解是作用在Field字段上,运行时生效,触发的是IdentityCardNumber这个验证类。

  • message 定制化的提示信息,主要是从ValidationMessages.properties里提取,也可以依据实际情况进行定制
  • groups 这里主要进行将validator进行分类,不同的类group中会执行不同的validator操作
  • payload 主要是针对bean的,使用不多。

 

2. 然后自定义Validator

这个是真正进行验证的逻辑代码:

public class IdentityCardNumberValidator implements ConstraintValidator<IdentityCardNumber, Object> {

    @Override
    public void initialize(IdentityCardNumber identityCardNumber) {
    }

    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        return IdCardValidatorUtils.isValidate18Idcard(o.toString());
    }
}

IdCardValidatorUtils在项目源码中,可自行查看

 

3. 使用自定义的注解

   @NotBlank(message = "身份证号不能为空")
    @IdentityCardNumber(message = "身份证信息有误,请核对后提交")
    private String clientCardNo;

 

4.使用groups的校验

有的宝宝说同一个对象要复用,比如UserDTO在更新时候要校验userId,在保存的时候不需要校验userId,在两种情况下都要校验username,那就用上groups了:

先定义groups的分组接口CreateUpdate

import javax.validation.groups.Default;

public interface Create extends Default {
}

import javax.validation.groups.Default;

public interface Update extends Default{
}

再在需要校验的地方@Validated声明校验组

/**
     * 走参数校验注解的 groups 组合校验
     *
     * @param userDTO
     * @return
     */
    @PostMapping("/update/groups")
    public RspDTO update(@RequestBody @Validated(Update.class) UserDTO userDTO) {
        userService.updateById(userDTO);
        return RspDTO.success();
    }

在DTO中的字段上定义好groups = {}的分组类型

@Data
public class UserDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    /*** 用户ID*/
    @NotNull(message = "用户id不能为空", groups = Update.class)
    private Long userId;

    /**
     * 用户名
     */
    @NotBlank(message = "用户名不能为空")
    @Length(max = 20, message = "用户名不能超过20个字符", groups = {Create.class, Update.class})
    @Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "用户昵称限制:最多20字符,包含文字、字母和数字")
    private String username;

    /**
     * 手机号
     */
    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误", groups = {Create.class, Update.class})
    private String mobile;

    /**
     * 性别
     */
    private String sex;

    /**
     * 邮箱
     */
    @NotBlank(message = "联系邮箱不能为空")
    @Email(message = "邮箱格式不对")
    private String email;

    /**
     * 密码
     */
    private String password;

    /*** 创建时间 */
    @Future(message = "时间必须是将来时间", groups = {Create.class})
    private Date createTime;

}

  注意:在声明分组的时候尽量加上 extend javax.validation.groups.Default 否则,在你声明@Validated(Update.class) 的时候,就会出现你在默认没添加groups = {}的时候的校验组@Email(message = "邮箱格式不对"),会不去校验,因为默认的校验组是groups = {Default.class}.

 

5.restful风格用法

在多个参数校验,或者@RequestParam 形式时候,需要在controller上加注@Validated

  @GetMapping("/get")
    public RspDTO getUser(@RequestParam("userId") @NotNull(message = "用户id不能为空") Long userId) {
        User user = userService.selectById(userId);
        if (user == null) {
            return new RspDTO<User>().nonAbsent("用户不存在");
        }
        return new RspDTO<User>().success(user);
    }
@RestController
@RequestMapping("user/")
@Validated
public class UserController extends AbstractController {

.... San Rocco Code ...

 

to sum up


  Very simple to use up, soEasy, unified structure focused on the participation of return, unified parameter check, is to reduce the amount of code of the magic we try catch, I think the project will deal with abnormal and abnormal good log management, sublimation is good, plain paper, just a rookie Advanced notes ....

 

Guess you like

Origin www.cnblogs.com/myseries/p/12098436.html