在做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;
}
}
效果
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());
}
}
}
使用資源檔案配置錯誤提示 和 國際化
- 國際化配置
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";
}
- 測試
切換語言
再次驗證
進一步通過反射優化使用時的配置
現在使用檢測時,字段名稱的國際化仍然需要指定國際化屬性檔案的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