springboot使用validator進行參數校驗

前言

公司項目是前後端分離的,爲了保證前端傳輸數據的合法性,對參數進行校驗就很有必要。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;
		}
	}
}

寫好的註解,和其他註解使用方法一樣,在上文中已經使用過了,再次不再贅述了。

寫在最後的話

參數校驗有三種使用方法,應該能滿足大部分情況下的使用。所以,就這樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章