天天看點

springboot使用hibernate validator校驗配置ValidatorConfigurationGET參數校驗(@RequestParam參數校驗)model校驗自定義校驗器使用資源檔案配置錯誤提示 和 國際化進一步通過反射優化使用時的配置JSR303定義的校驗類型常用驗證注解源碼

在做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;
    }

}
           

效果

springboot使用hibernate validator校驗配置ValidatorConfigurationGET參數校驗(@RequestParam參數校驗)model校驗自定義校驗器使用資源檔案配置錯誤提示 和 國際化進一步通過反射優化使用時的配置JSR303定義的校驗類型常用驗證注解源碼

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());
        }
    }
}           
springboot使用hibernate validator校驗配置ValidatorConfigurationGET參數校驗(@RequestParam參數校驗)model校驗自定義校驗器使用資源檔案配置錯誤提示 和 國際化進一步通過反射優化使用時的配置JSR303定義的校驗類型常用驗證注解源碼

使用資源檔案配置錯誤提示 和 國際化

  • 國際化配置
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";
    }           
  • 測試
    springboot使用hibernate validator校驗配置ValidatorConfigurationGET參數校驗(@RequestParam參數校驗)model校驗自定義校驗器使用資源檔案配置錯誤提示 和 國際化進一步通過反射優化使用時的配置JSR303定義的校驗類型常用驗證注解源碼

切換語言

springboot使用hibernate validator校驗配置ValidatorConfigurationGET參數校驗(@RequestParam參數校驗)model校驗自定義校驗器使用資源檔案配置錯誤提示 和 國際化進一步通過反射優化使用時的配置JSR303定義的校驗類型常用驗證注解源碼

再次驗證

springboot使用hibernate validator校驗配置ValidatorConfigurationGET參數校驗(@RequestParam參數校驗)model校驗自定義校驗器使用資源檔案配置錯誤提示 和 國際化進一步通過反射優化使用時的配置JSR303定義的校驗類型常用驗證注解源碼

進一步通過反射優化使用時的配置

現在使用檢測時,字段名稱的國際化仍然需要指定國際化屬性檔案的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