Java業(yè)務(wù)校驗(yàn)工具實(shí)現(xiàn)方法
一、背景
在我們?nèi)粘=涌陂_(kāi)發(fā)過(guò)程中,可能要面對(duì)一些稍微復(fù)雜一些的業(yè)務(wù)邏輯代碼的編寫(xiě),在執(zhí)行真正的業(yè)務(wù)邏輯前,往往要進(jìn)行一系列的前期校驗(yàn)工作,校驗(yàn)可以分為參數(shù)合法性校驗(yàn)和業(yè)務(wù)數(shù)據(jù)校驗(yàn)。
參數(shù)合法性校驗(yàn)比如最常見(jiàn)的校驗(yàn)參數(shù)值非空校驗(yàn)、格式校驗(yàn)、最大值最小值校驗(yàn)等,可以通過(guò)Hibernate Validator框架實(shí)現(xiàn),本文不具體講解。業(yè)務(wù)數(shù)據(jù)校驗(yàn)通常與實(shí)際業(yè)務(wù)相關(guān),比如提交訂單接口,我們可能需要校驗(yàn)商品是否合法、庫(kù)存是否足夠、客戶余額是否足夠、還有其他的一些風(fēng)控校驗(yàn)。我們的代碼可能看起來(lái)像是這樣的:
public ApiResult<OrderSubmitVo> submitOrder(OrderSubmitDto orderSubmitDto) { // 業(yè)務(wù)校驗(yàn)1 // 業(yè)務(wù)校驗(yàn)2 // 業(yè)務(wù)校驗(yàn)3 // 業(yè)務(wù)校驗(yàn)n... // 執(zhí)行真正的業(yè)務(wù)邏輯 return ApiResult.success(); }
二、問(wèn)題
實(shí)現(xiàn)不夠優(yōu)雅
上述代碼在版本迭代的過(guò)程中,還可能陸陸續(xù)續(xù)增加/修改一些校驗(yàn)邏輯,如果業(yè)務(wù)邏輯校驗(yàn)的代碼都耦合在核心業(yè)務(wù)邏輯中,這樣實(shí)現(xiàn)其實(shí)是不夠優(yōu)雅,不符合設(shè)計(jì)原則的單一職責(zé)原則和開(kāi)閉原則。
校驗(yàn)代碼無(wú)法復(fù)用
如果某個(gè)業(yè)務(wù)校驗(yàn)代碼需要在其他業(yè)務(wù)中也會(huì)用到,那我們則需要將相同的代碼復(fù)制一份至業(yè)務(wù)代碼中,比如校驗(yàn)用戶狀態(tài),在很多業(yè)務(wù)校驗(yàn)中都需要校驗(yàn),如果校驗(yàn)邏輯有些許更改的話,那么所有涉及到的地方都要同步修改,這樣不利于系統(tǒng)維護(hù)。
校驗(yàn)邏輯無(wú)法按照順序依賴執(zhí)行,并且校驗(yàn)過(guò)程中產(chǎn)生的數(shù)據(jù)后續(xù)獲取不便
如果我們將上述代碼中的各個(gè)校驗(yàn)邏輯封裝成獨(dú)立的子方法,那有可能存在業(yè)務(wù)校驗(yàn)2要依賴于業(yè)務(wù)校驗(yàn)1的數(shù)據(jù)結(jié)果,并且在業(yè)務(wù)校驗(yàn)過(guò)程中產(chǎn)生的數(shù)據(jù)在后續(xù)執(zhí)行真正的業(yè)務(wù)邏輯的時(shí)候是需要用得到的。
三、校驗(yàn)工具實(shí)現(xiàn)思路
我們要寫(xiě)的校驗(yàn)工具至少要解決上面所說(shuō)的三個(gè)問(wèn)題
- 業(yè)務(wù)校驗(yàn)代碼與核心業(yè)務(wù)邏輯代碼解耦
- 同一個(gè)校驗(yàn)器可以用于多個(gè)業(yè)務(wù),提高代碼的復(fù)用性和可維護(hù)性
- 校驗(yàn)代碼可以按照指定順序執(zhí)行,并且校驗(yàn)過(guò)程中產(chǎn)生的數(shù)據(jù)可以后續(xù)傳遞
在用zuul來(lái)做網(wǎng)關(guān)服務(wù)的時(shí)候,我獲得了一些靈感,zuul中的filterType用來(lái)區(qū)分請(qǐng)求路由到目標(biāo)之前、處理目標(biāo)請(qǐng)求、目標(biāo)請(qǐng)求返回后的類(lèi)型,filterOrder用來(lái)指定過(guò)濾器的執(zhí)行順序,RequestContext為請(qǐng)求上下文,RequestContext繼承自ConcurrentHashMap,且與ThreadLocal綁定保證線程安全,請(qǐng)求上下文中的數(shù)據(jù)在一次請(qǐng)求的所有過(guò)濾器中可以獲取,很好的完成了數(shù)據(jù)傳遞。
首先我們需要定義一個(gè)校驗(yàn)器注解,注解中指定業(yè)務(wù)類(lèi)型和執(zhí)行順序,在校驗(yàn)器上加上該注解表明這是一個(gè)校驗(yàn)器。定義一個(gè)校驗(yàn)器上下文,在業(yè)務(wù)校驗(yàn)執(zhí)行過(guò)程中產(chǎn)生的數(shù)據(jù)可以通過(guò)上下文進(jìn)行傳遞。定義一個(gè)校驗(yàn)器基類(lèi),校驗(yàn)器繼承基類(lèi),并實(shí)現(xiàn)其中的具體校驗(yàn)方法。定義一個(gè)校驗(yàn)器的統(tǒng)一執(zhí)行器,執(zhí)行器可以根據(jù)業(yè)務(wù)類(lèi)型找出所有帶有校驗(yàn)器注解并且是指定業(yè)務(wù)類(lèi)型的校驗(yàn)器列表,根據(jù)校驗(yàn)器注解中的執(zhí)行順序排序后,遍歷所有校驗(yàn)器列表調(diào)用校驗(yàn)方法。如果校驗(yàn)過(guò)程中校驗(yàn)失敗,則拋出校驗(yàn)異常中斷業(yè)務(wù)執(zhí)行。
以上為大概的實(shí)現(xiàn)思路,具體的實(shí)現(xiàn)代碼如下:
四、show me your code
Validator.java
import java.lang.annotation.*; /** * @author: 會(huì)跳舞的機(jī)器人 * @date: 2019/10/23 13:58 * @description: 業(yè)務(wù)校驗(yàn)注解 */ @Documented @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Validator { /** * 業(yè)務(wù)類(lèi)型,同一個(gè)校驗(yàn)器可以指定多個(gè)業(yè)務(wù)類(lèi)型 * * @return */ String[] validateTypes(); /** * 執(zhí)行順序,數(shù)值越小越先執(zhí)行 * * @return */ int validateOrder(); }
Validator校驗(yàn)注解,在校驗(yàn)器的類(lèi)上加上該注解則表明為業(yè)務(wù)校驗(yàn)器,validateTypes表示業(yè)務(wù)類(lèi)型,同一個(gè)校驗(yàn)器可以指定多個(gè)業(yè)務(wù)類(lèi)型,多個(gè)業(yè)務(wù)類(lèi)型可以復(fù)用同一個(gè)校驗(yàn)器,validateOrder表示執(zhí)行順序,數(shù)值越小越先被執(zhí)行。
ValidatorContext.java
import java.util.concurrent.ConcurrentHashMap; /** * @author: 會(huì)跳舞的機(jī)器人 * @date: 2019/9/11 14:56 * @description: 校驗(yàn)器上下文,與當(dāng)前線程綁定 */ public class ValidatorContext extends ConcurrentHashMap<String, Object> { /** * 請(qǐng)求對(duì)象 */ public Object requestDto; protected static final ThreadLocal<? extends ValidatorContext> threadLocal = ThreadLocal.withInitial(() -> new ValidatorContext()); /** * 獲取當(dāng)前線程的上下文 * * @return */ public static ValidatorContext getCurrentContext() { ValidatorContext context = threadLocal.get(); return context; } /** * 設(shè)值 * * @param key * @param value */ public void set(String key, Object value) { if (value != null) put(key, value); else remove(key); } /** * 獲取String值 * * @param key * @return */ public String getString(String key) { return (String) get(key); } /** * 獲取Integer值 * * @param key * @return */ public Integer getInteger(String key) { return (Integer) get(key); } /** * 獲取Boolean值 * * @param key * @return */ public Boolean getBoolean(String key) { return (Boolean) get(key); } /** * 獲取對(duì)象 * * @param key * @param <T> * @return */ public <T> T getClazz(String key) { return (T) get(key); } /** * 獲取Long值 * * @param key * @return */ public Long getLong(String key) { return (Long) get(key); } public <T> T getRequestDto() { return (T) requestDto; } public void setRequestDto(Object requestDto) { this.requestDto = requestDto; }
ValidatorContext為請(qǐng)求上下文,與當(dāng)前請(qǐng)求線程綁定,繼承自ConcurrentHashMap,requestDto屬性為接口請(qǐng)求入?yún)?duì)象,提供get/set方法使得在上下文中能更加便捷的獲取請(qǐng)求入?yún)?shù)據(jù)。
ValidatorTemplate.java
/** * @author: 會(huì)跳舞的機(jī)器人 * @date: 2019/10/23 11:51 * @description: 校驗(yàn)器模板,業(yè)務(wù)校驗(yàn)器需繼承模板類(lèi) */ @Slf4j @Component public abstract class ValidatorTemplate { /** * 校驗(yàn)方法 */ public void validate() { try { validateInner(); } catch (ValidateException e) { log.error("業(yè)務(wù)校驗(yàn)失敗", e); throw e; } catch (Exception e) { log.error("業(yè)務(wù)校驗(yàn)異常", e); ValidateException validateException = new ValidateException(ResultEnum.VALIDATE_ERROR); throw validateException; } } /** * 校驗(yàn)方法,由子類(lèi)具體實(shí)現(xiàn) * * @throws ValidateException */ protected abstract void validateInner() throws ValidateException; }
校驗(yàn)器抽象類(lèi),具體的校驗(yàn)器需要繼承該類(lèi),并且實(shí)現(xiàn)具體的validateInner校驗(yàn)方法。
ValidatorTemplateProxy.java
/** * @author: 會(huì)跳舞的機(jī)器人 * @date: 2019/10/25 18:03 * @description: ValidatorTemplate代理類(lèi) */ @Data @AllArgsConstructor public class ValidatorTemplateProxy extends ValidatorTemplate implements Comparable<ValidatorTemplateProxy> { private ValidatorTemplate validatorTemplate; private String validateType; private int validateOrder; @Override public int compareTo(ValidatorTemplateProxy o) { return Integer.compare(this.getValidateOrder(), o.getValidateOrder()); } @Override protected void validateInner() throws ValidateException { validatorTemplate.validateInner(); } }
ValidatorTemplate類(lèi)的代理類(lèi),實(shí)現(xiàn)了Comparable排序接口,便于校驗(yàn)器按照validateOrder屬性排序,并且將校驗(yàn)器中的注解轉(zhuǎn)化為代理類(lèi)中的兩個(gè)屬性字段,方便執(zhí)行過(guò)程中的統(tǒng)一日志打印。
ValidateProcessor.java
import java.lang.annotation.Annotation; import java.util.*; /** * @author: 會(huì)跳舞的機(jī)器人 * @date: 2019/10/25 18:02 * @description: 執(zhí)行器 */ @Slf4j @Component public class ValidateProcessor { /** * 執(zhí)行業(yè)務(wù)類(lèi)型對(duì)應(yīng)的校驗(yàn)器 * * @param validateType */ public void validate(String validateType) { if (StringUtils.isEmpty(validateType)) { throw new IllegalArgumentException("validateType cannot be null"); } long start = System.currentTimeMillis(); log.info("start validate,validateType={},ValidatorContext={}", validateType, ValidatorContext.getCurrentContext().toString()); List<ValidatorTemplateProxy> validatorList = getValidatorList(validateType); if (CollectionUtils.isEmpty(validatorList)) { log.info("validatorList is empty"); return; } ValidatorTemplateProxy validateProcessorProxy; for (ValidatorTemplateProxy validatorTemplate : validatorList) { validateProcessorProxy = validatorTemplate; log.info("{} is running", validateProcessorProxy.getValidatorTemplate().getClass().getSimpleName()); validatorTemplate.validate(); } log.info("end validate,validateType={},ValidatorContext={},time consuming {} ms", validateType, ValidatorContext.getCurrentContext().toString(), (System.currentTimeMillis() - start)); } /** * 根據(jù)Validator注解的validateType獲取所有帶有該注解的校驗(yàn)器 * * @param validateType * @return */ private List<ValidatorTemplateProxy> getValidatorList(String validateType) { List<ValidatorTemplateProxy> validatorTemplateList = new LinkedList<>(); Map<String, Object> map = SpringUtil.getApplicationContext().getBeansWithAnnotation(Validator.class); String[] validateTypes; int validateOrder; Annotation annotation; for (Map.Entry<String, Object> item : map.entrySet()) { annotation = item.getValue().getClass().getAnnotation(Validator.class); validateTypes = ((Validator) annotation).validateTypes(); validateOrder = ((Validator) annotation).validateOrder(); if (item.getValue() instanceof ValidatorTemplate) { if (Arrays.asList(validateTypes).contains(validateType)) { validatorTemplateList.add(new ValidatorTemplateProxy((ValidatorTemplate) item.getValue(), validateType, validateOrder)); } } else { log.info("{}not extend from ValidatorTemplate", item.getKey()); } } Collections.sort(validatorTemplateList); return validatorTemplateList; } }
業(yè)務(wù)校驗(yàn)的執(zhí)行器,getValidatorList方法根據(jù)validateType值獲取所有帶有該validateType值的校驗(yàn)器,并將其封裝成ValidatorTemplateProxy代理類(lèi),然后再做排序。validate為統(tǒng)一的業(yè)務(wù)校驗(yàn)方法。
ValidateException.java
/** * @author: 會(huì)跳舞的機(jī)器人 * @date: 2019/4/4 6:34 PM * @description: 校驗(yàn)異常 */ 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; } }
ValidateException為校驗(yàn)失敗時(shí),拋出的業(yè)務(wù)校驗(yàn)異常類(lèi)。
ValidateTypeConstant.java
/** * @author: 會(huì)跳舞的機(jī)器人 * @date: 2019/10/30 15:16 * @description: */ public class ValidateTypeConstant { /** * 提交訂單校驗(yàn) */ public static final String ORDER_SUBMIT = "order_submit"; }
ValidateTypeConstant為定義validateType業(yè)務(wù)校驗(yàn)類(lèi)型的常量類(lèi)。
五、使用樣例
以訂單提交為例,我們首先定義了兩個(gè)個(gè)基本的校驗(yàn)器,下單商品信息校驗(yàn)器、客戶狀態(tài)校驗(yàn)器,均為偽代碼實(shí)現(xiàn)。
OrderSubmitProductValidator.java
/** * @author: 會(huì)跳舞的機(jī)器人 * @date: 2019/10/30 15:34 * @description: 商品狀態(tài)以及庫(kù)存校驗(yàn) */ @Component @Slf4j @Validator(validateTypes = ValidateTypeConstant.ORDER_SUBMIT, validateOrder = 1) public class OrderSubmitProductValidator extends ValidatorTemplate { @Override protected void validateInner() throws ValidateException { ValidatorContext validatorContext = ValidatorContext.getCurrentContext(); OrderSubmitDto orderSubmitDto = validatorContext.getRequestDto(); // 獲取商品信息并校驗(yàn)商品狀態(tài) List<ProductShelfVo> productShelfVoList = new ArrayList<>(); if (0 == 1) { throw new ValidateException("商品已下架"); } // 將商品信息設(shè)置至上下文中 validatorContext.set("productShelfVoList", productShelfVoList); } }
OrderSubmitCustomerValidator.java
/** * @author: 會(huì)跳舞的機(jī)器人 * @date: 2019/10/30 19:24 * @description: */ @Component @Slf4j @Validator(validateTypes = ValidateTypeConstant.ORDER_SUBMIT, validateOrder = 2) public class OrderSubmitCustomerValidator extends ValidatorTemplate { @Override protected void validateInner() throws ValidateException { ValidatorContext validatorContext = ValidatorContext.getCurrentContext(); String customerNo = validatorContext.getString("customerNo"); if (StringUtils.isEmpty(customerNo)) { throw new IllegalArgumentException("客戶編號(hào)為空"); } // 獲取客戶信息并校驗(yàn)客戶狀態(tài) CustomerVo customer = new CustomerVo(); if (0 == 1) { throw new ValidateException("客戶限制交易"); } } }
在提交訂單的業(yè)務(wù)邏輯的代碼中使用:
/** * 提交訂單 * * @param orderSubmitDto * @return */ public ApiResult<OrderSubmitVo> submitOrder(OrderSubmitDto orderSubmitDto) { // 業(yè)務(wù)校驗(yàn) ValidatorContext validatorContext = ValidatorContext.getCurrentContext(); validatorContext.setRequestDto(orderSubmitDto); validateProcessor.validate(ValidateTypeConstant.ORDER_SUBMIT); // 從上下文中獲取下單商品信息 List<ProductShelfVo> productShelfVoList = validatorContext.getClazz("productShelfVoList"); // 后續(xù)業(yè)務(wù)邏輯處理 return ApiResult.success(); }
通過(guò)使用上述封裝的校驗(yàn)工具后,業(yè)務(wù)代碼與校驗(yàn)代碼解耦,后續(xù)要增加/修改業(yè)務(wù)校驗(yàn)邏輯時(shí)候,我們只需要增加/修改相應(yīng)的校驗(yàn)器即可,不必改動(dòng)到主業(yè)務(wù)邏輯。為了我們能更簡(jiǎn)單和方便找到某個(gè)業(yè)務(wù)邏輯對(duì)應(yīng)所有的校驗(yàn)器,我們?cè)诿r?yàn)器的時(shí)候可以加上業(yè)務(wù)類(lèi)型的前綴。
六、總結(jié)
1、在開(kāi)發(fā)過(guò)程中,我們遇到一些“煩人”問(wèn)題的時(shí)候,要想辦法解決它,而不是忽略不管它,通過(guò)解決問(wèn)題可以提高我們的技術(shù)能力。
2、要善于從其他優(yōu)秀的技術(shù)框架學(xué)習(xí)其實(shí)現(xiàn)思路。
3、以上校驗(yàn)工具只是一個(gè)簡(jiǎn)單實(shí)現(xiàn),解決的問(wèn)題只是筆者在開(kāi)發(fā)過(guò)程中遇到的問(wèn)題,可能并不一定具有通用性。
到此這篇關(guān)于Java業(yè)務(wù)校驗(yàn)工具實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)Java 業(yè)務(wù)校驗(yàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- windows環(huán)境下java開(kāi)發(fā)工具maven的安裝教程圖解
- Java http請(qǐng)求封裝工具類(lèi)代碼實(shí)例
- Java并發(fā)工具輔助類(lèi)代碼實(shí)例
- Java工具類(lèi)BeanUtils庫(kù)介紹及實(shí)例詳解
- Java常用類(lèi)庫(kù)Apache Commons工具類(lèi)說(shuō)明及使用實(shí)例詳解
- JAVA 16位ID生成工具類(lèi)含16位不重復(fù)的隨機(jī)數(shù)數(shù)字+大小寫(xiě)
- java開(kāi)發(fā)時(shí)各類(lèi)工具的使用規(guī)范
相關(guān)文章
Maven+oracle+SSM搭建簡(jiǎn)單項(xiàng)目的方法
本篇文章主要介紹了Maven+oracle+SSM搭建簡(jiǎn)單項(xiàng)目的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03Java項(xiàng)目的目錄結(jié)構(gòu)詳解
本文主要介紹了Java項(xiàng)目的目錄結(jié)構(gòu)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02Java后端SSM框架圖片上傳功能實(shí)現(xiàn)方法解析
這篇文章主要介紹了Java后端SSM框架圖片上傳功能實(shí)現(xiàn)方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06java實(shí)現(xiàn)簡(jiǎn)單汽車(chē)租賃系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單汽車(chē)租賃系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-01-01Java中的Random和ThreadLocalRandom詳細(xì)解析
這篇文章主要介紹了Java中的Random和ThreadLocalRandom詳細(xì)解析,Random 類(lèi)用于生成偽隨機(jī)數(shù)的流, 該類(lèi)使用48位種子,其使用線性同余公式進(jìn)行修改,需要的朋友可以參考下2024-01-01java、springboot?接口導(dǎo)出txt方式
這篇文章主要介紹了java、springboot?接口導(dǎo)出txt方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01Java享元設(shè)計(jì)模式優(yōu)化對(duì)象創(chuàng)建提高性能和效率
Java享元設(shè)計(jì)模式通過(guò)共享可重用的對(duì)象,減少了系統(tǒng)中對(duì)象的數(shù)量,優(yōu)化了對(duì)象的創(chuàng)建和管理,提高了性能和效率。它是一種經(jīng)典的設(shè)計(jì)模式,適用于需要處理大量相似對(duì)象的應(yīng)用程序2023-04-04在SpringBoot中實(shí)現(xiàn)線程池并行處理任務(wù)的方法詳解
在使用Spring Boot開(kāi)發(fā)應(yīng)用程序時(shí),我們經(jīng)常需要處理一些耗時(shí)的任務(wù),例如網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)庫(kù)操作或者其他需要花費(fèi)一定時(shí)間的計(jì)算任務(wù),本文將介紹如何在Spring Boot中使用線程池來(lái)實(shí)現(xiàn)任務(wù)的并行處理2023-06-06通過(guò)java反射機(jī)制動(dòng)態(tài)調(diào)用某方法的總結(jié)(推薦)
下面小編就為大家?guī)?lái)一篇通過(guò)java反射機(jī)制動(dòng)態(tài)調(diào)用某方法的總結(jié)(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-07-07