一、背景
在前面的文章分享了一篇自已關(guān)于Java業(yè)務(wù)校驗(yàn)工具的實(shí)現(xiàn)Java業(yè)務(wù)校驗(yàn)工具實(shí)現(xiàn),后面本著“不要重復(fù)造輪子”的原則,在網(wǎng)上搜索果然有志同道合的朋友已經(jīng)實(shí)現(xiàn)過相同的功能框架fluent-validator。
在大致看完整體功能與大概實(shí)現(xiàn)后,覺得這是一個(gè)不錯(cuò)的校驗(yàn)框架,但對于我現(xiàn)在的使用場景來說,會感覺有一些“重量級”,即有些功能對我們的使用場景來說有些多余,因?yàn)槲覀兊氖褂脠鼍氨容^簡單直接,即在執(zhí)行真正的業(yè)務(wù)邏輯前做業(yè)務(wù)規(guī)則校驗(yàn),校驗(yàn)不通過則直接返回。本著“簡單的就是最好的”原則,所以摘取了fluent-validator的一部分代碼寫了一個(gè)簡單版本的fluent-validator,總共類只有6個(gè),F(xiàn)luentValidator.java、Validator.java、ValidatorContext.java、ValidatorElement.java、ValidatorElementList.java、ValidateException.java。以下為全部代碼:
二、show me your code
- FluentValidator.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 鏈?zhǔn)秸{(diào)用驗(yàn)證器,參考fluent-validator的簡單實(shí)現(xiàn)
* https://github.com/neoremind/fluent-validator
*
* @author 會跳舞的機(jī)器人
* @date 2019/12/27
*/
public class FluentValidator {
private static final Logger logger = LoggerFactory.getLogger(FluentValidator.class);
/**
* 驗(yàn)證器鏈,惰性求值期間就是不斷的改變這個(gè)鏈表,及時(shí)求值期間就是遍歷鏈表依次執(zhí)行驗(yàn)證
*/
private ValidatorElementList validatorElementList = new ValidatorElementList();
/**
* 驗(yàn)證器上下文
*/
private ValidatorContext context = new ValidatorContext();
/**
* 私有構(gòu)造方法,只能通過checkAll創(chuàng)建對象
*/
private FluentValidator() {
}
/**
* 創(chuàng)建FluentValidator對象
*
* @return
*/
public static FluentValidator checkAll() {
return new FluentValidator();
}
/**
* 使用驗(yàn)證器進(jìn)行驗(yàn)證
*
* @param validator 驗(yàn)證器
* @return
*/
public <T> FluentValidator on(Validator<T> validator) {
validatorElementList.add(new ValidatorElement(null, validator));
return this;
}
/**
* 使用驗(yàn)證器驗(yàn)證指定對象
*
* @param t 待驗(yàn)證對象
* @param validator 驗(yàn)證器
* @return
*/
public <T> FluentValidator on(T t, Validator<T> validator) {
validatorElementList.add(new ValidatorElement(t, validator));
return this;
}
/**
* 使用驗(yàn)證器驗(yàn)證指定對象
*
* @param t 待驗(yàn)證對象
* @param validator 驗(yàn)證器
* @param condition 條件,為true時(shí)才會將驗(yàn)證器加入驗(yàn)證器列表中
* @return
*/
public <T> FluentValidator on(T t, Validator<T> validator, boolean condition) {
if (condition) {
validatorElementList.add(new ValidatorElement(t, validator));
}
return this;
}
/**
* 執(zhí)行各個(gè)驗(yàn)證器中的驗(yàn)證邏輯
*
* @return
*/
public FluentValidator doValidate() {
if (validatorElementList.isEmpty()) {
logger.info("Nothing to validate");
return null;
}
long start = System.currentTimeMillis();
logger.info("Start to validate,validatorElementList={}", validatorElementList.toString());
String validatorName;
try {
for (ValidatorElement element : validatorElementList.getList()) {
Object target = element.getTarget();
Validator validator = element.getValidator();
validatorName = validator.getClass().getSimpleName();
logger.info("{} is running", validatorName);
validator.validate(context, target);
}
} catch (ValidateException e) {
throw e;
} catch (Exception e) {
throw e;
} finally {
logger.info("End to validate,time consuming {} ms", (System.currentTimeMillis() - start));
}
return this;
}
/**
* 將鍵值對放入上下文
*
* @param key 鍵
* @param value 值
* @return FluentValidator
*/
public FluentValidator putAttribute2Context(String key, Object value) {
if (context == null) {
context = new ValidatorContext();
}
context.setAttribute(key, value);
return this;
}
/**
* 獲取驗(yàn)證器上下文
*
* @return
*/
public ValidatorContext getContext() {
return context;
}
}
- Validator.java
/**
* 驗(yàn)證器接口, 泛型T表示待驗(yàn)證對象的類型
*
* @author 會跳舞的機(jī)器人
* @date 2019/12/27
*/
public interface Validator<T> {
/**
* 執(zhí)行驗(yàn)證,如果驗(yàn)證失敗,則拋出ValidateException異常
*
* @param context 驗(yàn)證上下文
* @param t 待驗(yàn)證對象
*/
void validate(ValidatorContext context, T t);
}
- ValidatorContext.java
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
/**
* 驗(yàn)證器在執(zhí)行調(diào)用過程中的上下文
* 1.驗(yàn)證器中的數(shù)據(jù)傳遞共享
* 2.驗(yàn)證結(jié)果數(shù)據(jù)緩存以作后續(xù)使用
*
* @author 會跳舞的機(jī)器人
* @date 2019/12/27
*/
public class ValidatorContext {
/**
* 驗(yàn)證器均可以共享使用的屬性鍵值對
*/
private Map<String, Object> attributes;
/**
* 設(shè)置屬性值
*
* @param key 鍵
* @param value 值
*/
public void setAttribute(String key, Object value) {
if (attributes == null) {
attributes = new HashMap<>();
}
attributes.put(key, value);
}
/**
* 獲取String值
*
* @param key
* @return
*/
public String getString(String key) {
return (String) getAttribute(key);
}
/**
* 獲取Integer值
*
* @param key
* @return
*/
public Integer getInteger(String key) {
return (Integer) getAttribute(key);
}
/**
* 獲取Boolean值
*
* @param key
* @return
*/
public Boolean getBoolean(String key) {
return (Boolean) getAttribute(key);
}
/**
* 獲取Long值
*
* @param key
* @return
*/
public Long getLong(String key) {
return (Long) getAttribute(key);
}
/**
* 獲取BigDecimal值
*
* @param key
* @return
*/
public BigDecimal getBigDecimal(String key) {
return (BigDecimal) getAttribute(key);
}
/**
* 獲取對象
*
* @param key
* @param <T>
* @return
*/
public <T> T getClazz(String key) {
return (T) getAttribute(key);
}
/**
* 獲取屬性
*
* @param key 鍵
* @return 值
*/
public Object getAttribute(String key) {
if (attributes != null && !attributes.isEmpty()) {
return attributes.get(key);
}
return null;
}
}
- ValidatorElement.java
/**
* 驗(yàn)證器包裝類
*
* @author 會跳舞的機(jī)器人
* @date 2019/12/27
*/
public class ValidatorElement {
/**
* 待驗(yàn)證對象
*/
private Object target;
/**
* 驗(yàn)證器
*/
private Validator validator;
public ValidatorElement(Object target, Validator validator) {
this.target = target;
this.validator = validator;
}
public Object getTarget() {
return target;
}
public Validator getValidator() {
return validator;
}
}
- ValidatorElementList.java
import java.util.LinkedList;
/**
* 在FluentValidator內(nèi)部調(diào)用使用的驗(yàn)證器鏈
*
* @author 會跳舞的機(jī)器人
* @date 2019/12/27
*/
public class ValidatorElementList {
/**
* 驗(yàn)證器鏈表
*/
private LinkedList<ValidatorElement> validatorElementLinkedList = new LinkedList<>();
/**
* 將驗(yàn)證器加入鏈表
*
* @param element
*/
public void add(ValidatorElement element) {
validatorElementLinkedList.add(element);
}
/**
* 獲取驗(yàn)證器鏈表
*
* @return
*/
public LinkedList<ValidatorElement> getList() {
return validatorElementLinkedList;
}
/**
* 驗(yàn)證器鏈表是否為空
*
* @return
*/
public boolean isEmpty() {
return validatorElementLinkedList.isEmpty();
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
for (ValidatorElement element : validatorElementLinkedList) {
sb.append("[");
sb.append(element.getValidator().getClass().getSimpleName());
sb.append("]->");
}
return sb.toString();
}
}
- ValidateException.java
/**
* 校驗(yàn)異常
*
* @author 會跳舞的機(jī)器人
* @date 2019/4/4
*/
public class ValidateException extends RuntimeException {
// 異常碼
private Integer code;
public ValidateException() {
}
public ValidateException(String message) {
super(message);
}
public ValidateException(ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
三、使用樣例
- 校驗(yàn)器定義
@Component
public class CustomerSignValidator implements Validator<String> {
@Override
public void validate(ValidatorContext context, String s) {
// 模擬獲取客戶信息并進(jìn)行校驗(yàn)
Customer customer = new Customer();
if (1 == 2) {
throw new ValidateException("校驗(yàn)客戶狀態(tài)失敗");
}
// 將客戶信息存入上下文以便后續(xù)使用
context.setAttribute("customer", customer);
}
}
- 使用FluentValidator
// step1.業(yè)務(wù)規(guī)則校驗(yàn)
FluentValidator fluentValidator = FluentValidator.checkAll()
.on(tradeTimeValidator) // 判斷交易時(shí)間
.on(riskTradeStatusValidator) // 判斷交易風(fēng)控狀態(tài)
.on(tradeValidateBo, productTradeStatusValidator) // 交易商品狀態(tài)判斷
.on(tradeValidateBo, tradeCountValidator) // 判斷交易商品數(shù)量是否正確
.on(dto.getCustomerNo(), customerSignValidator) // 判斷客戶簽約開戶狀態(tài)
.on(buyerTradeStatusValidator) // 判斷客戶交易狀態(tài)
.on(tradeValidateBo, entrustOrderValidator) // 判斷委托單是否合法
.on(tradeValidateBo, buyerBalanceValidator) // 判斷買入資金是否足夠
.doValidate();
// 從校驗(yàn)器上下文中獲取產(chǎn)生的數(shù)據(jù)
CustomerVo customerVo = fluentValidator.getContext().getClazz("customer");
以上代碼即可實(shí)現(xiàn)各個(gè)業(yè)務(wù)規(guī)則的校驗(yàn),校驗(yàn)過程中產(chǎn)生的數(shù)據(jù)可以存放在context上下文中,后續(xù)要用到這部分?jǐn)?shù)據(jù)可以直接從context中獲取。
實(shí)際運(yùn)行例子如下:
[INFO] [http-nio-9006-exec-8] [com.xxxx.validator.FluentValidator:72] - Start to validate,validatorElementList=[TradeTimeValidator]->[RiskTradeStatusValidator]->[ProductTradeStatusValidator]->[TradeCountValidator]->[PriceValidator]->[CustomerSignValidator]->[BuyerTradeStatusValidator]->[BuyerBalanceValidator]->
[INFO] [http-nio-9006-exec-8] [com.xxxx.validator.FluentValidator:79] - TradeTimeValidator is running
[INFO] [http-nio-9006-exec-8] [com.xxxx.validator.FluentValidator:79] - RiskTradeStatusValidator is running
[INFO] [http-nio-9006-exec-8] [com.xxxx.validator.FluentValidator:79] - ProductTradeStatusValidator is running
[INFO] [http-nio-9006-exec-8] [com.xxxx.validator.FluentValidator:79] - TradeCountValidator is running
[INFO] [http-nio-9006-exec-8] [com.xxxx.validator.FluentValidator:79] - PriceValidator is running
[INFO] [http-nio-9006-exec-8] [com.xxxx.validator.FluentValidator:79] - CustomerSignValidator is running
[INFO] [http-nio-9006-exec-8] [com.xxxx.validator.FluentValidator:79] - BuyerTradeStatusValidator is running
[INFO] [http-nio-9006-exec-8] [com.xxxx.validator.FluentValidator:79] - BuyerBalanceValidator is running
[INFO] [http-nio-9006-exec-8] [com.xxxx.validator.FluentValidator:87] - End to validate,time consuming 24 ms
四、總結(jié)
以上的校驗(yàn)工具滿足了我們當(dāng)前的需要,我覺得在一般的場景是夠用的。另外能想到的擴(kuò)展場景包括:有需要?jiǎng)討B(tài)的配置業(yè)務(wù)校驗(yàn)器的話,可以通過將校驗(yàn)器類名寫配置文件中,使用的時(shí)候在spring容器中通過類名獲取各個(gè)校驗(yàn)器實(shí)例后,再去執(zhí)行校驗(yàn)邏輯。如果想要更好的管理各個(gè)業(yè)務(wù)對應(yīng)的校驗(yàn)器以及校驗(yàn)器的啟用與停用狀態(tài)的話,可以考慮做一個(gè)管理界面,在界面中展示某個(gè)業(yè)務(wù)與業(yè)務(wù)對應(yīng)的各個(gè)校驗(yàn)器,可以開啟或者停止校驗(yàn)器。
如果文章對你有幫助的話,給文章點(diǎn)個(gè)贊吧。
如果有寫得不正確的地方,歡迎指出。
文章首發(fā)公眾號:會跳舞的機(jī)器人,歡迎掃碼關(guān)注。