前言
公司項目是前後端分離的,爲了保證前端傳輸數據的合法性,對參數進行校驗就很有必要。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 |
被註釋的元素必須是電子郵箱地址 | |
@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;
}
}
}
寫好的註解,和其他註解使用方法一樣,在上文中已經使用過了,再次不再贅述了。
寫在最後的話
參數校驗有三種使用方法,應該能滿足大部分情況下的使用。所以,就這樣。