1. 程式人生 > >springboot-No6 : 校驗的引入 @Constraint和自定義註解進行校驗策略的設計模式淺談

springboot-No6 : 校驗的引入 @Constraint和自定義註解進行校驗策略的設計模式淺談

之前寫的註解校驗的不足

前面寫到如何使用自定義的註解進行校驗,@FieldInfo  

主要做法是在註解中加入一些需要校驗的資訊

然後呢 編寫解析註解的方法,然後我們寫統一的校驗策略來進行校驗。

最後在統一的呼叫這個校驗演算法來達到校驗的目的

但是上面這個過程呢我們並不知道使用該註解進行校驗的策略有多少個。

而且如果需要加入新的校驗策略時候,那個呼叫校驗策略的類還是需要修改的。、

比如 增加了一個校驗策略,那麼原先呼叫的校驗策略如果沒有使用策略模式的話,肯定要加入新的校驗邏輯的.

這個關鍵是要在呼叫我們的校驗策略類那裡下功夫。校驗策略類可以使用模板方法模式或者策略模式來進行設計

目標是加入新的校驗策略我們不用該呼叫的邏輯

事件程式設計給我們的啟示

事件程式設計我們寫好這個響應函式之後,註冊給監聽器,但是邏輯上這段程式碼並沒有立馬就被呼叫執行

而是等待的事件觸發之後回撥的。

其實呢,校驗也是一樣,如果我們編寫了一個儲存了校驗資訊的註解,( 注意: 範圍有些大,

或者我們根據欄位來進行區分,針對某一種欄位做一個註解,比如身份證號碼的註解資訊,這樣我們可以將校驗的範圍縮小,有利於我們的實現控制 。註解資訊的拆分成各個小的註解類)

我們可以將該註解需要進行校驗的策略繫結到該註解上面,這樣我們可以知道這個註解上到底使用了哪些校驗演算法。

如果我們的校驗演算法有擴充,只要新加入新的校驗策略,然後註冊到該註解的繫結校驗策略的集合上,就可以進行新的校驗

我們其他的地方都不要改動了。符合了設計模式的 修改的封閉,進行插入來達到功能的修改。

奧義:  將策略模式應用到註解本身,給註解註冊能夠提供校驗的策略類

@Constraint 註解的威力

我們的註解如果想來進行校驗欄位,那麼依賴 @Constraint 是很不錯的選擇的

@Documented
@Target({ ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface Constraint {
    Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
}

其中的 validatedBy屬性指定了需要進行校驗的策略類集合,這是一個數組。

也就是說我們定義的註解可以使用@Constraint 進行修飾指定校驗策略.

陣列的型別是  ConstraintValidator.上面的兩個??是啥呢?看看ConstraintValidator的程式碼:

ConstraintValidator.java

public interface ConstraintValidator<A extends Annotation, T> {
    void initialize(A constraintAnnotation);
    boolean isValid(T value, ConstraintValidatorContext context);
}

第一個是我們的註解,也就是我們自己定義的註解,第二個是引數的值

ConstraintValidator是一個介面,也就是我們自己定義的校驗類要實現這個介面

使用@Constraint 

使用@Constraint 修飾我們的註解

例如 我們 校驗手機號,可以寫一個  @Mobile的註解

Mobile.java

package miaosha.annotation;

import java.lang.annotation.Target;

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

import miaosha.validator.MobileValidator;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;

/**
 * 做一個mobile的註解 注意我們使用的靜態匯入
 * 
 * @author kaifeng1
 *
 */
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { MobileValidator.class })
public @interface Mobile {
	boolean required() default true;

	String message() default "手機號碼格式錯誤";

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

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

需要自定義一個校驗器 MobileValidator,它使用 ValidatorUtil來完成校驗的具體邏輯

MobileValidator.java

package miaosha.validator;

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

import org.apache.commons.lang3.StringUtils;

import miaosha.annotation.Mobile;
import miaosha.util.ValidatorUtil;

public class MobileValidator implements ConstraintValidator<Mobile, String> {
	private boolean required = false;
	/**
	 * 初始化
	 */
	public void initialize(Mobile constraintAnnotation) {
		required = constraintAnnotation.required();
	}

	/**
	 * 校驗
	 */
	public boolean isValid(String value, ConstraintValidatorContext context) {
		if (required) {
			return ValidatorUtil.isMobile(value);
		} else {
			if (StringUtils.isEmpty(value)) {
				return true;
			} else {
				return ValidatorUtil.isMobile(value);
			}
		}
	}

}

ValidatorUtil.java

package miaosha.util;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;

/**
 * 校驗
 * 
 * @author kaifeng1
 *
 */
public class ValidatorUtil {

	private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}");

	public static boolean isMobile(String src) {
		if (StringUtils.isEmpty(src)) {
			return false;
		}
		Matcher m = mobile_pattern.matcher(src);
		return m.matches();
	}
}

我們使用該註解的時候:

	@Mobile
	private String mobile;

最終呼叫點

我們可以使用  spring-boot-starter-validation 框架來進行校驗

使用 @Valid 標籤即可

這個註解純粹是 標記介面,裡面什麼也沒有

@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface Valid {
}

例如 我們在controller的入口引數上面加入,@Valid修飾我們的引數

這樣校驗就可以生效了

主要使用的是:

org.springframework.validation 來進行的

	@RequestMapping("/doLogin")
	@ResponseBody
	Result<Boolean> doLogin(@Valid LoginVo vo) {
		log.info(vo.toString());
		
		return Result.sucess(Boolean.TRUE);
	}

但是呢如果被校驗住的的時候這時候是會出錯的。

這裡會丟擲這個:

org.springframework.validation.BindException

這個異常時呼叫我們controller之前就發生的,因此需要異常攔截機制

所以我們必須要進行異常的攔截才可以發現這些資訊的.

在下節中介紹加入異常的攔截機制.