springboot使用hibernate validator校验

简介: 在做web相关的应用时,经常需要提供接口与用户交互(获取数据、上传数据等),由于这个过程需要用户进行相关的操作,为了避免出现一些错误的数据等,一般需要对数据进行校验,随着接口的增多,校验逻辑的冗余度也越来越大,虽然可以通过抽象出校验的方法来处理,但还是需要每次手动调用校验逻辑,相对来说还是不方便。

在做web相关的应用时,经常需要提供接口与用户交互(获取数据、上传数据等),由于这个过程需要用户进行相关的操作,为了避免出现一些错误的数据等,一般需要对数据进行校验,随着接口的增多,校验逻辑的冗余度也越来越大,虽然可以通过抽象出校验的方法来处理,但还是需要每次手动调用校验逻辑,相对来说还是不方便。

为了解决这个问题,Java中提供了Bean Validation的标准,该标准规定了校验的具体内容,通过简单的注解就能完成必要的校验逻辑了,相对来说就方便了很多,而该规范其实只是规范,并没有具体的实现,Hibernate提供了具体的实现,也即Hibernate Validator,这个也是目前使用得比较多的验证器了。

Bean Validation 1.0(JSR-303)
http://jcp.org/en/jsr/detail?id=303

Bean Validation 1.1(JSR-349)
http://jcp.org/en/jsr/detail?id=349.

@Valid是使用hibernate validation的时候使用
java的jsr303声明了这类接口,hibernate-validator对其进行了实现

@Validated 是只用spring Validator 校验机制使用

配置ValidatorConfiguration

package com.example.validation.config;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
public class ValidatorConfiguration {


    
    /**
     * Method:  开启快速返回
     * Description: 如果参数校验有异常,直接抛异常,不会进入到 controller,使用全局异常拦截进行拦截
     *
     * @param
     * @return org.springframework.validation.beanvalidation.MethodValidationPostProcessor
     */
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
        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;
    }
}



package com.example.validation.web.advice;

import java.util.List;
import java.util.stream.Collectors;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
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 org.springframework.web.bind.annotation.ResponseStatus;

@Component
@ControllerAdvice
public class GlobalExceptionHandlerAdvice {
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultInfo<?> validationErrorHandler(MethodArgumentNotValidException ex) {
        // 同样是获取BindingResult对象,然后获取其中的错误信息
        // 如果前面开启了fail_fast,事实上这里只会有一个信息
        //如果没有,则可能又多个
        List<String> errorInformation = ex.getBindingResult().getAllErrors()
                .stream()
                .map(ObjectError::getDefaultMessage)
                .collect(Collectors.toList());
        return new ResultInfo<>(400, errorInformation.toString(), null);
    }
    
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public ResultInfo<?> validationErrorHandler(ConstraintViolationException ex) {
        List<String> errorInformation = ex.getConstraintViolations()
                .stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.toList());
        return new ResultInfo<>(400, errorInformation.toString(), null);
    }
}



package com.example.validation.web.advice;

public class ResultInfo<T> {
    private int code;
    private String message;
    private T body;

    public ResultInfo(int code, String message, T body) {
        this.code = code;
        this.message = message;
        this.body = body;
    }

    public ResultInfo(int code, String message) {
        this(code, message, null);
    }

    public ResultInfo(String message) {
        this(200, message, null);
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getBody() {
        return body;
    }

    public void setBody(T body) {
        this.body = body;
    }
    
    
}

GET参数校验(@RequestParam参数校验)

RequestParam参数的校验需要使用 @Validated 注解,进行支持,该注解可以注解在类、方法、参数上

package com.example.validation.web;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.example.validation.web.dto.User;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@RestController("testController")
@Api(tags = "用户管理")
@RequestMapping("/api/user/")
@Validated //使用spring的validation
public class UserController {
    
    @GetMapping("/id/{userId}")
    @ApiOperation(value = "用户管理-查询用户-根据userId查询", code = 200)
    public User getUserByUserId(@PathVariable String userId) {
        
        User user = new User();
        user.setUserId(userId);
        return user;
    }
    /**
     * PathVariable启用注解
     * RequestParam启用注解
     * @param name
     * @param params
     * @return
     */
    @GetMapping("/name/{name}")
    @ApiOperation(value = "用户管理-查询用户-根据userName查询", code = 200)
    public User getUserByName(
                    @NotNull 
                    @Size(min = 1, max = 20, message = "用户名格式有误")
                    @PathVariable String name, 
                    
                    @NotNull 
                    @Size(min = 1, max = 20, message = "params用户名格式有误")
                    @RequestParam String params) {
        User user = new User();
        user.setUserId("userName," + params);
//        user.setName(name);
        return user;
    }
    
    @PostMapping()
    @ApiOperation(value = "用户管理-添加用户", code = 200)
    public User save(@RequestBody @Valid User user) {
        return user;
    }

}

效果
image

model校验

package com.example.validation.web.dto;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import com.example.validation.web.validator.CaseMode;
import com.example.validation.web.validator.CheckCase;


public class User {
    @Size(min = 6,max = 16,message = "userId长度")
    @NotNull(message = "userId长度不能为空")
    private String userId;
    
    @CheckCase(value = CaseMode.LOWER,message = "userName必须是小写")
    private String userNameLower;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserNameLower() {
        return userNameLower;
    }

    public void setUserNameLower(String userNameLower) {
        this.userNameLower = userNameLower;
    }



    
    
    
}




package com.example.validation.web;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.example.validation.web.dto.User;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@RestController("testController")
@Api(tags = "用户管理")
@RequestMapping("/api/user/")
@Validated //使用spring的validation
public class UserController {
    
    @GetMapping("/id/{userId}")
    @ApiOperation(value = "用户管理-查询用户-根据userId查询", code = 200)
    public User getUserByUserId(@PathVariable String userId) {
        
        User user = new User();
        user.setUserId(userId);
        return user;
    }
    /**
     * PathVariable启用注解
     * RequestParam启用注解
     * @param name
     * @param params
     * @return
     */
    @GetMapping("/name/{name}")
    @ApiOperation(value = "用户管理-查询用户-根据userName查询", code = 200)
    public User getUserByName(
                    @NotNull 
                    @Size(min = 1, max = 20, message = "用户名格式有误")
                    @PathVariable String name, 
                    
                    @NotNull 
                    @Size(min = 1, max = 20, message = "params用户名格式有误")
                    @RequestParam String params) {
        User user = new User();
        user.setUserId("userName," + params);
//        user.setName(name);
        return user;
    }
    
    @PostMapping()
    @ApiOperation(value = "用户管理-添加用户", code = 200)
    public User save(@RequestBody @Valid User user) {
        return user;
    }

}

自定义校验器

package com.example.validation.web.validator;

public enum CaseMode {
    UPPER,
    LOWER;
}


package com.example.validation.web.validator;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface CheckCase {
    String message() default "";

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

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

    CaseMode value();
}


package com.example.validation.web.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
    private CaseMode caseMode;
    public void initialize(CheckCase checkCase) {
        this.caseMode = checkCase.value();
    }

    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if (s == null) {
            return true;
        }

        if (caseMode == CaseMode.UPPER) {
            return s.equals(s.toUpperCase());
        } else {
            return s.equals(s.toLowerCase());
        }
    }
}

效果
image

使用资源文件配置错误提示 和 国际化

  • 国际化配置
package com.example.validation.config;

import java.util.Locale;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class MessageSourceAndLocaleConfiguration extends WebMvcConfigurerAdapter{
    @Value("i18n/messages_validate")
    private String i18nMessages;
    @Bean(name = "messageSource")
    public ResourceBundleMessageSource getMessageSource() throws Exception {
        ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
        resourceBundleMessageSource.setDefaultEncoding("UTF-8");
//        resourceBundleMessageSource.setBasenames("i18n/messages_validate");
        resourceBundleMessageSource.setBasenames(i18nMessages);
        return resourceBundleMessageSource;
    }
    
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver slr = new SessionLocaleResolver();
        // 默认语言
        slr.setDefaultLocale(Locale.CHINA);
        return slr;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        // 参数名
        lci.setParamName("lang");
        return lci;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
}
  • 配置validation处理拦截器和资源文件地址

package com.example.validation.config;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.validation.Validator;

import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
public class ValidatorConfiguration {

    @Autowired
    ResourceBundleMessageSource messageSource;
    /**
     * Method:  开启快速返回
     * Description: 如果参数校验有异常,直接抛异常,不会进入到 controller,使用全局异常拦截进行拦截
     *
     * @param
     * @return org.springframework.validation.beanvalidation.MethodValidationPostProcessor
     */
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
        postProcessor.setValidator(validator());
        return postProcessor;
    }

    @Bean
    public Validator validator(){
        /*ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                .configure()
                .messageInterpolator(new ResourceBundleMessageInterpolator(new PlatformResourceBundleLocator("i18n/messages_validate" )))
                // 将fail_fast设置为true即可,如果想验证全部,则设置为false或者取消配置即可
                .failFast( true )
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();
        return validator;*/
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
//        方式一
//        try {
//            validator.setValidationMessageSource(messageSource);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
        validator.setMessageInterpolator(new MessageInterpolator(new PlatformResourceBundleLocator("i18n/messages_validate" )));
//        方式二
        return validator;
    }
    
    
    private class MessageInterpolator extends ResourceBundleMessageInterpolator {
        @SuppressWarnings("unused")
        MessageInterpolator(){
            
        }
        
        MessageInterpolator(ResourceBundleLocator resourceBundleLocator){
            super(resourceBundleLocator);
        }

        
        @Override
        public String interpolate(String message, Context context, Locale locale) {
            // 获取注解类型
            String annotationTypeName = context.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName();
            
            // 根据注解类型获取自定义的消息Code
            String annotationDefaultMessageCode = VALIDATION_ANNATATION_DEFAULT_MESSAGES.get(annotationTypeName);
            if (null != annotationDefaultMessageCode && !message.startsWith("javax.validation")
                    && !message.startsWith("org.hibernate.validator.constraints")) {
                // 如果注解上指定的message不是默认的javax.validation或者org.hibernate.validator等开头的情况,
                // 则需要将自定义的消息Code拼装到原message的后面;
                message += "{" + annotationDefaultMessageCode + "}";
            }
            
            return super.interpolate(message, context, locale);
        }
    }
    
    private static final Map<String, String> VALIDATION_ANNATATION_DEFAULT_MESSAGES =
            new HashMap<String, String>(20) {{
                put("CheckCase", "demo.validation.constraints.CheckCase.LOWER.message");
                put("NotNull", "demo.validation.constraints.NotNull.message");
            }};

    
}
  • 使用
package com.example.validation.web.dto;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import com.example.validation.web.validator.CaseMode;
import com.example.validation.web.validator.CheckCase;


public class User2 {
    @Size(min = 6,max = 16)
    @NotNull(message = "{userId}")
    private String userId;
    
    @CheckCase(value = CaseMode.LOWER,message = "{userName}")
    private String userNameLower;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserNameLower() {
        return userNameLower;
    }

    public void setUserNameLower(String userNameLower) {
        this.userNameLower = userNameLower;
    }
}
  • 在UserController配置一个语言切换的端点
@GetMapping("/i18n")
    public String changeSessionLanauage(HttpServletRequest request, String lang){
        System.out.println(lang);
        if("zh_CN".equals(lang)){
            //代码中即可通过以下方法进行语言设置
            request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME,new Locale("zh","CN"));
        }else if("en_US".equals(lang)){
            request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME,new Locale("en","US"));
        }
        return "hello";
    }
  • 测试
    image

切换语言
image
再次验证
image

进一步通过反射优化使用时的配置

现在使用检测时,字段名称的国际化仍然需要指定国际化属性文件的key值

@CheckCase(value = CaseMode.LOWER,message = "{userName}")
    private String userNameLower;

可以通过一定的规则(如字段名去属性文件中查找),进一步简化成

@CheckCase(value = CaseMode.LOWER)
    private String userNameLower;

待进一步补充

JSR303定义的校验类型

空检查

@Null       验证对象是否为null

@NotNull    验证对象是否不为null, 无法查检长度为0的字符串

@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.

@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

 

Booelan检查

@AssertTrue     验证 Boolean 对象是否为 true  

@AssertFalse    验证 Boolean 对象是否为 false  

 

长度检查

@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  

@Length(min=, max=) Validates that the annotated string is between min and max included.

 

日期检查

@Past           验证 Date 和 Calendar 对象是否在当前时间之前  

@Future     验证 Date 和 Calendar 对象是否在当前时间之后  

@Pattern    验证 String 对象是否符合正则表达式的规则

 

数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null

@Min            验证 Number 和 String 对象是否大等于指定的值  

@Max            验证 Number 和 String 对象是否小等于指定的值  

@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度

@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度

@Digits     验证 Number 和 String 的构成是否合法  

@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。

 

@Range(min=, max=) Checks whether the annotated value lies between (inclusive) the specified minimum and maximum.

@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage;

 

@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)

@CreditCardNumber信用卡验证

@Email  验证是否是邮件地址,如果为null,不进行验证,算通过验证。

@ScriptAssert(lang= ,script=, alias=)

@URL(protocol=,host=, port=,regexp=, flags=)

常用验证注解

@Null,标注的属性值必须为空

@NotNull,标注的属性值不能为空

@AssertTrue,标注的属性值必须为true

@AssertFalse,标注的属性值必须为false

@Min,标注的属性值不能小于min中指定的值

@Max,标注的属性值不能大于max中指定的值

@DecimalMin,小数值,同上

@DecimalMax,小数值,同上

@Negative,负数

@NegativeOrZero,0或者负数

@Positive,整数

@PositiveOrZero,0或者整数

@Size,指定字符串长度,注意是长度,有两个值,min以及max,用于指定最小以及最大长度

@Digits,内容必须是数字

@Past,时间必须是过去的时间

@PastOrPresent,过去或者现在的时间

@Future,将来的时间

@FutureOrPresent,将来或者现在的时间

@Pattern,用于指定一个正则表达式

@NotEmpty,字符串内容非空

@NotBlank,字符串内容非空且长度大于0

@Email,邮箱

@Range,用于指定数字,注意是数字的范围,有两个值,min以及max

源码

https://github.com/renchenglin/spring-cloud

相关文章
|
1月前
|
安全 前端开发 Java
Springboot中如何优雅的进行字段以及业务校验
Springboot中如何优雅的进行字段以及业务校验
|
4月前
|
Java Spring
SpringBoot - 优雅的实现【应用启动参数校验】
SpringBoot - 优雅的实现【应用启动参数校验】
61 0
|
2月前
|
Java 数据库 数据安全/隐私保护
【SpringBoot】Validator组件+自定义约束注解实现手机号码校验和密码格式限制
【SpringBoot】Validator组件+自定义约束注解实现手机号码校验和密码格式限制
131 1
|
4天前
|
前端开发 Java
SpringBoot之自定义注解参数校验
SpringBoot之自定义注解参数校验
15 2
|
4天前
|
存储 JSON 算法
SpringBoot之JWT令牌校验
SpringBoot之JWT令牌校验
11 2
|
20天前
|
JSON Java 数据格式
Spring Boot实现各种参数校验
这些是Spring Boot中实现参数校验的一些常见方法,你可以根据项目需求选择适合的方式来进行参数校验。
16 0
|
2月前
|
存储 NoSQL 前端开发
【SpringBoot】Redis集中管理Session和自定义用户参数解决登录状态及校验问题
【SpringBoot】Redis集中管理Session和自定义用户参数解决登录状态及校验问题
|
2月前
|
前端开发 Java Maven
spring boot3参数校验基本用法
spring boot3参数校验基本用法
51 2
|
3月前
|
Java 关系型数据库 MySQL
【极光系列】springBoot集成Hibernate
【极光系列】springBoot集成Hibernate
|
4月前
|
Java 数据库连接 Spring
Springboot校验注解
Spring Boot 提供了一组基于 Hibernate Validator 的校验注解,用于验证请求参数、实体对象等数据的合法性。下面是一些常用的 Spring Boot 校验注解及其功能: