hibernate validator校验

转载:https://blog.csdn.net/waynelee0809/article/details/90711049

前言
公司项目是前后端分离的,为了保证前端传输数据的合法性,对参数进行校验就很有必要。hibernate-validator就是一个不错的参数校验的解决方法。spring-boot-starter-web包里面有hibernate-validator的包,所以不需要引用hibernate validator依赖,直接就可以用。

一、配置validator
Validator是javax包下的一个接口,hibernate对其进行了一系列的实现。我们需要构建Validator,注入spring中,然后就可以直接引用使用了。

先写个配置类,配置一下Validator。

failFast(true)的意思是快速失败,当检测到第一处不符合的时候就直接返回,不再校验下一个参数。

MethodValidationPostProcessor是controller层参数校验必须的一个Bean。

@Configuration
public class ValidatorConfig {
 
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
        /**设置validator模式为快速失败返回*/
        postProcessor.setValidator(validator());
        return postProcessor;
    }
    
    @Bean
    public Validator validator(){
        ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                .configure()
                .failFast(true)
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();
 
        return validator;
    }
    
}
二、设置校验规则
大多数时候,参数校验是对象属性的校验,所以需要对对象的属性设置校验的规则,java和hibernate提供了一系列注解来帮助我们实现规则的设置。

注解    释义
@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提供
@NotBlank(message =)    验证字符串非null,且长度必须大于0
@Email    被注释的元素必须是电子邮箱地址
@Length(min=,max=)    被注释的字符串的大小必须在指定的范围内
@NotEmpty    被注释的字符串的必须非空
@Range(min=,max=,message=)    被注释的元素必须在合适的范围内
写一个实体类,使用这些注解,@IsPhone是自定义的注解,后面会说到。

public class User {
    
    private Integer id;
    
    @NotBlank(message="手机号不能为空")
    @IsPhone
    private String phone;
    
    @NotEmpty(message = "用户名不能为空")
    private String username;
    
    @Range(min=1,max=200,message="年龄不能小于1大于200")
    private Integer age;
 
    //省略setter和getter
}
二、@RequestBody 请求参数校验
当使用标准的Restful格式的请求时,参数是json格式,参数会自动转为对象。在@RequestBody后面加个@Valid注解就可以进行参数校验了,参数里添加一个BindingResult就可以接受参数校验结果了。当有错误时,可以返回错误结果响应前端了。

    @PutMapping("/user")
    public ResponseEntity<Object> changeUser(@RequestBody @Valid User user,BindingResult result)throws Exception{
        
            if(result.hasErrors()){
                for (ObjectError error : result.getAllErrors()) {
                    System.out.println(error.getDefaultMessage());
                }
            }
 
        return ResponseEntity.ok().build();
    }
 
三、@RequestParam 请求参数校验
但是当我们使用@RequestParam来接收参数时,再使用上面的方法就不太好使了。毕竟没有自动生成实体类,无法校验实体类里属性的规则。这时候就需要直接在请求参数上进行校验了。

首先,我们要在类上加一个@Validated注解,然后再使用第二部的注解来标明参数的规则。

@RestController
@Validated
public class UserController {
 
    @GetMapping("/user")
    public ResponseEntity<Object> getUser(@NotBlank(message="手机号不能为空")
                                        @IsPhone(message="手机号格式不正确")
                                        @RequestParam String phone,
                                        @NotEmpty(message="用户名不能为空")
                                        @RequestParam String username,
                                    
                                        @Range(min = 1, max = 200, message = "年龄范围为1-200")
                                        @RequestParam Integer age)throws Exception{
        
        
        return ResponseEntity.ok().build();
    }
}
当参数不符合规范的时候,就会抛出ConstraintViolationException,所以我们就要捕获这个异常,响应前端。因为设置了快速失败,所以msg信息里只会有一条错误信息。

@ControllerAdvice
@Component
@Order(1)
public class ValidatorExceptionHandler {
    
    @ExceptionHandler(value=ValidationException.class)
    @ResponseBody
    public Object exceptionHandler(ValidationException e,HttpServletRequest request){
        
        String msg = new String();
        if(e instanceof ConstraintViolationException){
            ConstraintViolationException exs = (ConstraintViolationException) e;
 
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
                msg = item.getMessage();
            }
        }
        return ResponseEntity.badRequest().body(msg);
    }
 
}
四、自定义校验过程
如果要校验的参数不是请求参数或者以上方法都不适用,我们也可以自定义校验的过程。直接注入Validator进行校验即可。

首先写一个存放校验结果的类

import org.apache.commons.lang3.StringUtils;
 
public class ValidationResultBO {
 
    private boolean hasErrors = false;
    
    private Map<String,String> errorMsgMap = new HashMap<String, String>();
 
    //返回错误信息
    public String getErrorMsg(){
        return StringUtils.join(errorMsgMap.values().toArray(),",");
    }
 
    //省略setter和getter
    
}
然后自定义校验过程

@Component
public class ValidatorImpl{
    
    @Autowired
    private Validator validator;
 
    
    /**
     * 实现校验方式并返回检验结果
     * @param value
     * @return
     */
    public ValidationResultBO validate(Object value){
        ValidationResultBO result = new ValidationResultBO();
        Set<ConstraintViolation<Object>> validateSet = validator.validate(value);
        if (!validateSet.isEmpty()) {
            result.setHasErrors(true);
            
            validateSet.forEach( (validation) -> {
                String errorMsg = validation.getMessage();
                String propertyName = validation.getPropertyPath().toString();
                result.getErrorMsgMap().put(propertyName, errorMsg);
            });
        }
        
        return result;
    }
 
}
这样我们就可以在任何spring管理的类里直接注入ValidatorImpl来进行参数校验了。当校验结果ValidationResultBO里hasErrors为true时,就可以做相应的处理了。

举个栗子:

@Service
public class UserService {
 
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
 
    @Autowired
    private ValidatorImpl validator;
    
    public ReturnDataDTO<Object> addUser(String phone,String username,Integer age){
        
        User user = new User();
        user.setPhone(phone);
        user.setUsername(username);
        user.setAge(age);
        
        logger.info("{}",user);
        
        ValidationResultBO validate = validator.validate(user);
        if (validate.isHasErrors()) {
            String errorMsg = validate.getErrorMsg();
            logger.info("错误信息:{}",errorMsg);
            throw new MyException(111,errorMsg);
        }
        
        return ReturnDataDTO.ok();
    }
    
}
@RestController
public class UserController {
 
    @Autowired
    private UserService userService;
    
    @PostMapping("/user")
    public ResponseEntity<Object> addUser(@RequestParam String phone,
                                    @RequestParam String username,
                                    @RequestParam Integer age)throws Exception{
        
        userService.addUser(phone, username, age);
        
        return ResponseEntity.ok().build();
    }
 
}
ReturnDataDTO只是封装的返回参数,随便什么都可以。

MyException是全局异常处理自定义的异常,继承RuntimeException。

五、自定义校验注解
有时候我们也会需要其他的校验规则,但是官方没有,怎么办?这个简单,自己实现呗。

照着官方注解的样子写一个。

@Target({ElementType.METHOD,ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,
    ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {IsPhoneValidator.class})
public @interface IsPhone {
 
    String message() default "手机号格式错误";
    
    Class<?>[] groups() default {};
    
    Class<? extends Payload>[] payload() default {};
}
最主要的还是@Constraint这个注解,它指定了实现校验的类。这个类实现了ConstraintValidator接口,主要是重写isValid方法来进行校验。 

ConstraintValidator<IsPhone,String>有两个泛型,第一个是自定义的注解,也就是说自定义注解和实现校验的类是互相指定的关系。第二个是校验参数的类型,这里是校验手机号,所以是String。

public class IsPhoneValidator implements ConstraintValidator<IsPhone,String>{
    
    @Override
    public void initialize(IsPhone constraintAnnotation) {
        
    }
 
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        
        
        if ("".equals(value) || value.equals(null)) {
            return false;
        }else{
            return isPhone(value);
        }
        
    }
 
    /** 验证是否为手机号
     * @param phone
     * @return
     */
    public static boolean isPhone (String phone){
        String pattern  = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$";
        if (Pattern.matches(pattern, phone)) {
            return true;
        }else{
            return false;
        }
    }
}
写好的注解,和其他注解使用方法一样,在上文中已经使用过了,再次不再赘述了。

写在最后的话
参数校验有三种使用方法,应该能满足大部分情况下的使用。所以,就这样
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值