最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java業(yè)務(wù)校驗工具實現(xiàn)方法

 更新時間:2020年06月17日 09:24:51   作者:會跳舞的機器人  
這篇文章主要介紹了Java業(yè)務(wù)校驗工具實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、背景

在我們?nèi)粘=涌陂_發(fā)過程中,可能要面對一些稍微復(fù)雜一些的業(yè)務(wù)邏輯代碼的編寫,在執(zhí)行真正的業(yè)務(wù)邏輯前,往往要進行一系列的前期校驗工作,校驗可以分為參數(shù)合法性校驗和業(yè)務(wù)數(shù)據(jù)校驗。

參數(shù)合法性校驗比如最常見的校驗參數(shù)值非空校驗、格式校驗、最大值最小值校驗等,可以通過Hibernate Validator框架實現(xiàn),本文不具體講解。業(yè)務(wù)數(shù)據(jù)校驗通常與實際業(yè)務(wù)相關(guān),比如提交訂單接口,我們可能需要校驗商品是否合法、庫存是否足夠、客戶余額是否足夠、還有其他的一些風(fēng)控校驗。我們的代碼可能看起來像是這樣的:

public ApiResult<OrderSubmitVo> submitOrder(OrderSubmitDto orderSubmitDto) {
  // 業(yè)務(wù)校驗1

  // 業(yè)務(wù)校驗2

  // 業(yè)務(wù)校驗3

  // 業(yè)務(wù)校驗n...

  // 執(zhí)行真正的業(yè)務(wù)邏輯

  return ApiResult.success();
}

二、問題

實現(xiàn)不夠優(yōu)雅

上述代碼在版本迭代的過程中,還可能陸陸續(xù)續(xù)增加/修改一些校驗邏輯,如果業(yè)務(wù)邏輯校驗的代碼都耦合在核心業(yè)務(wù)邏輯中,這樣實現(xiàn)其實是不夠優(yōu)雅,不符合設(shè)計原則的單一職責(zé)原則和開閉原則。

校驗代碼無法復(fù)用

如果某個業(yè)務(wù)校驗代碼需要在其他業(yè)務(wù)中也會用到,那我們則需要將相同的代碼復(fù)制一份至業(yè)務(wù)代碼中,比如校驗用戶狀態(tài),在很多業(yè)務(wù)校驗中都需要校驗,如果校驗邏輯有些許更改的話,那么所有涉及到的地方都要同步修改,這樣不利于系統(tǒng)維護。

校驗邏輯無法按照順序依賴執(zhí)行,并且校驗過程中產(chǎn)生的數(shù)據(jù)后續(xù)獲取不便

如果我們將上述代碼中的各個校驗邏輯封裝成獨立的子方法,那有可能存在業(yè)務(wù)校驗2要依賴于業(yè)務(wù)校驗1的數(shù)據(jù)結(jié)果,并且在業(yè)務(wù)校驗過程中產(chǎn)生的數(shù)據(jù)在后續(xù)執(zhí)行真正的業(yè)務(wù)邏輯的時候是需要用得到的。

三、校驗工具實現(xiàn)思路

我們要寫的校驗工具至少要解決上面所說的三個問題

  • 業(yè)務(wù)校驗代碼與核心業(yè)務(wù)邏輯代碼解耦
  • 同一個校驗器可以用于多個業(yè)務(wù),提高代碼的復(fù)用性和可維護性
  • 校驗代碼可以按照指定順序執(zhí)行,并且校驗過程中產(chǎn)生的數(shù)據(jù)可以后續(xù)傳遞

在用zuul來做網(wǎng)關(guān)服務(wù)的時候,我獲得了一些靈感,zuul中的filterType用來區(qū)分請求路由到目標之前、處理目標請求、目標請求返回后的類型,filterOrder用來指定過濾器的執(zhí)行順序,RequestContext為請求上下文,RequestContext繼承自ConcurrentHashMap,且與ThreadLocal綁定保證線程安全,請求上下文中的數(shù)據(jù)在一次請求的所有過濾器中可以獲取,很好的完成了數(shù)據(jù)傳遞。

首先我們需要定義一個校驗器注解,注解中指定業(yè)務(wù)類型和執(zhí)行順序,在校驗器上加上該注解表明這是一個校驗器。定義一個校驗器上下文,在業(yè)務(wù)校驗執(zhí)行過程中產(chǎn)生的數(shù)據(jù)可以通過上下文進行傳遞。定義一個校驗器基類,校驗器繼承基類,并實現(xiàn)其中的具體校驗方法。定義一個校驗器的統(tǒng)一執(zhí)行器,執(zhí)行器可以根據(jù)業(yè)務(wù)類型找出所有帶有校驗器注解并且是指定業(yè)務(wù)類型的校驗器列表,根據(jù)校驗器注解中的執(zhí)行順序排序后,遍歷所有校驗器列表調(diào)用校驗方法。如果校驗過程中校驗失敗,則拋出校驗異常中斷業(yè)務(wù)執(zhí)行。

以上為大概的實現(xiàn)思路,具體的實現(xiàn)代碼如下:

四、show me your code

Validator.java

import java.lang.annotation.*;

/**
 * @author: 會跳舞的機器人
 * @date: 2019/10/23 13:58
 * @description: 業(yè)務(wù)校驗注解
 */
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Validator {
  /**
   * 業(yè)務(wù)類型,同一個校驗器可以指定多個業(yè)務(wù)類型
   *
   * @return
   */
  String[] validateTypes();

  /**
   * 執(zhí)行順序,數(shù)值越小越先執(zhí)行
   *
   * @return
   */
  int validateOrder();
}

Validator校驗注解,在校驗器的類上加上該注解則表明為業(yè)務(wù)校驗器,validateTypes表示業(yè)務(wù)類型,同一個校驗器可以指定多個業(yè)務(wù)類型,多個業(yè)務(wù)類型可以復(fù)用同一個校驗器,validateOrder表示執(zhí)行順序,數(shù)值越小越先被執(zhí)行。

ValidatorContext.java

import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: 會跳舞的機器人
 * @date: 2019/9/11 14:56
 * @description: 校驗器上下文,與當(dāng)前線程綁定
 */
public class ValidatorContext extends ConcurrentHashMap<String, Object> {
  /**
   * 請求對象
   */
  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);
  }

  /**
   * 獲取對象
   *
   * @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為請求上下文,與當(dāng)前請求線程綁定,繼承自ConcurrentHashMap,requestDto屬性為接口請求入?yún)ο螅峁ゞet/set方法使得在上下文中能更加便捷的獲取請求入?yún)?shù)據(jù)。

ValidatorTemplate.java

/**
 * @author: 會跳舞的機器人
 * @date: 2019/10/23 11:51
 * @description: 校驗器模板,業(yè)務(wù)校驗器需繼承模板類
 */
@Slf4j
@Component
public abstract class ValidatorTemplate {

  /**
   * 校驗方法
   */
  public void validate() {
    try {
      validateInner();
    } catch (ValidateException e) {
      log.error("業(yè)務(wù)校驗失敗", e);
      throw e;
    } catch (Exception e) {
      log.error("業(yè)務(wù)校驗異常", e);
      ValidateException validateException = new ValidateException(ResultEnum.VALIDATE_ERROR);
      throw validateException;
    }
  }

  /**
   * 校驗方法,由子類具體實現(xiàn)
   *
   * @throws ValidateException
   */
  protected abstract void validateInner() throws ValidateException;
}

校驗器抽象類,具體的校驗器需要繼承該類,并且實現(xiàn)具體的validateInner校驗方法。

ValidatorTemplateProxy.java

/**
 * @author: 會跳舞的機器人
 * @date: 2019/10/25 18:03
 * @description: ValidatorTemplate代理類
 */
@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類的代理類,實現(xiàn)了Comparable排序接口,便于校驗器按照validateOrder屬性排序,并且將校驗器中的注解轉(zhuǎn)化為代理類中的兩個屬性字段,方便執(zhí)行過程中的統(tǒng)一日志打印。

ValidateProcessor.java

import java.lang.annotation.Annotation;
import java.util.*;

/**
 * @author: 會跳舞的機器人
 * @date: 2019/10/25 18:02
 * @description: 執(zhí)行器
 */
@Slf4j
@Component
public class ValidateProcessor {

  /**
   * 執(zhí)行業(yè)務(wù)類型對應(yīng)的校驗器
   *
   * @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獲取所有帶有該注解的校驗器
   *
   * @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ù)校驗的執(zhí)行器,getValidatorList方法根據(jù)validateType值獲取所有帶有該validateType值的校驗器,并將其封裝成ValidatorTemplateProxy代理類,然后再做排序。validate為統(tǒng)一的業(yè)務(wù)校驗方法。

ValidateException.java

/**
 * @author: 會跳舞的機器人
 * @date: 2019/4/4 6:34 PM
 * @description: 校驗異常
 */
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è)務(wù)校驗異常類。

ValidateTypeConstant.java

/**
 * @author: 會跳舞的機器人
 * @date: 2019/10/30 15:16
 * @description:
 */
public class ValidateTypeConstant {
  /**
   * 提交訂單校驗
   */
  public static final String ORDER_SUBMIT = "order_submit";
}

ValidateTypeConstant為定義validateType業(yè)務(wù)校驗類型的常量類。

五、使用樣例

以訂單提交為例,我們首先定義了兩個個基本的校驗器,下單商品信息校驗器、客戶狀態(tài)校驗器,均為偽代碼實現(xiàn)。

OrderSubmitProductValidator.java

/**
 * @author: 會跳舞的機器人
 * @date: 2019/10/30 15:34
 * @description: 商品狀態(tài)以及庫存校驗
 */
@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();
    // 獲取商品信息并校驗商品狀態(tài)
    List<ProductShelfVo> productShelfVoList = new ArrayList<>();
    if (0 == 1) {
      throw new ValidateException("商品已下架");
    }
    // 將商品信息設(shè)置至上下文中
    validatorContext.set("productShelfVoList", productShelfVoList);
  }
}

OrderSubmitCustomerValidator.java

/**
 * @author: 會跳舞的機器人
 * @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("客戶編號為空");
    }
    // 獲取客戶信息并校驗客戶狀態(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ù)校驗
  ValidatorContext validatorContext = ValidatorContext.getCurrentContext();
  validatorContext.setRequestDto(orderSubmitDto);
  validateProcessor.validate(ValidateTypeConstant.ORDER_SUBMIT);
  // 從上下文中獲取下單商品信息
  List<ProductShelfVo> productShelfVoList = validatorContext.getClazz("productShelfVoList");

  // 后續(xù)業(yè)務(wù)邏輯處理
  return ApiResult.success();
}

通過使用上述封裝的校驗工具后,業(yè)務(wù)代碼與校驗代碼解耦,后續(xù)要增加/修改業(yè)務(wù)校驗邏輯時候,我們只需要增加/修改相應(yīng)的校驗器即可,不必改動到主業(yè)務(wù)邏輯。為了我們能更簡單和方便找到某個業(yè)務(wù)邏輯對應(yīng)所有的校驗器,我們在命名校驗器的時候可以加上業(yè)務(wù)類型的前綴。

六、總結(jié)

1、在開發(fā)過程中,我們遇到一些“煩人”問題的時候,要想辦法解決它,而不是忽略不管它,通過解決問題可以提高我們的技術(shù)能力。

2、要善于從其他優(yōu)秀的技術(shù)框架學(xué)習(xí)其實現(xiàn)思路。

3、以上校驗工具只是一個簡單實現(xiàn),解決的問題只是筆者在開發(fā)過程中遇到的問題,可能并不一定具有通用性。

到此這篇關(guān)于Java業(yè)務(wù)校驗工具實現(xiàn)方法的文章就介紹到這了,更多相關(guān)Java 業(yè)務(wù)校驗內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • spring中@Bean和@Component的區(qū)別及說明

    spring中@Bean和@Component的區(qū)別及說明

    文章主要介紹了@Bean和@Component兩個注解在Spring框架中的定義、作用范圍、創(chuàng)建方式、掃描和識別機制以及使用場景和建議
    2024-12-12
  • 關(guān)于Spring中一級緩存、二級緩存和三級緩存的那些事

    關(guān)于Spring中一級緩存、二級緩存和三級緩存的那些事

    Spring解決循環(huán)依賴的核心思想在于提前曝,下面這篇文章主要給大家介紹了關(guān)于Spring中一級緩存、二級緩存和三級緩存的那些事,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-02-02
  • Java中Set集合的基本使用方法舉例

    Java中Set集合的基本使用方法舉例

    在Java中可以使用不同的實現(xiàn)類來創(chuàng)建和初始化Set集合,下面這篇文章主要給大家介紹了關(guān)于Java中Set集合的基本使用方法,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-07-07
  • java spring整合junit操作(有詳細的分析過程)

    java spring整合junit操作(有詳細的分析過程)

    這篇文章主要介紹了java spring整合junit操作(有詳細的分析過程),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • Java數(shù)據(jù)結(jié)構(gòu)之順序表詳解

    Java數(shù)據(jù)結(jié)構(gòu)之順序表詳解

    這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之順序表詳解,線性表在邏輯上是線性結(jié)構(gòu),也就說是連續(xù)的一條直線。但是在物理結(jié)構(gòu)上并不一定是連續(xù)的,線性表在物理上存儲時,通常以數(shù)組和鏈式結(jié)構(gòu)的形式存儲,需要的朋友可以參考下
    2023-07-07
  • Java?web實現(xiàn)簡單注冊功能

    Java?web實現(xiàn)簡單注冊功能

    這篇文章主要為大家詳細介紹了Java?web實現(xiàn)簡單注冊功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • java實現(xiàn)模擬RPG格斗

    java實現(xiàn)模擬RPG格斗

    這篇文章主要介紹了java實現(xiàn)模擬RPG格斗,每個英雄具有以下幾個屬性:生命值(為0時英雄倒下)、攻擊力(每次攻擊時扣除對方的生命值點數(shù))、攻擊間隔(每次攻擊過后都要等待間隔時間才能進行下次攻擊首次攻擊之前也要先等待間隔時間)簡單設(shè)置了下吧,小伙伴可以參考下
    2015-03-03
  • springboot返回前端中文亂碼的解決

    springboot返回前端中文亂碼的解決

    這篇文章主要介紹了springboot返回前端中文亂碼的解決,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • 教你Java中的Lock鎖底層AQS到底是如何實現(xiàn)的

    教你Java中的Lock鎖底層AQS到底是如何實現(xiàn)的

    本文是基于ReentrantLock來講解,ReentrantLock加鎖只是對AQS的api的調(diào)用,底層的鎖的狀態(tài)(state)和其他線程等待(Node雙向鏈表)的過程其實是由AQS來維護的,對Java?Lock鎖AQS實現(xiàn)過程感興趣的朋友一起看看吧
    2022-05-05
  • Java實現(xiàn)的讀取資源文件工具類ResourcesUtil實例【可動態(tài)更改值的內(nèi)容】

    Java實現(xiàn)的讀取資源文件工具類ResourcesUtil實例【可動態(tài)更改值的內(nèi)容】

    這篇文章主要介紹了Java實現(xiàn)的讀取資源文件工具類ResourcesUtil,結(jié)合實例形式分析了java針對資源文件的讀取與修改相關(guān)操作技巧,需要的朋友可以參考下
    2017-10-10

最新評論

元阳县| 佛教| 商都县| 鄄城县| 淳安县| 兴山县| 天柱县| 汽车| 海门市| 贞丰县| 河间市| 古交市| 泰州市| 富阳市| 晋宁县| 个旧市| 金溪县| 淮北市| 遂川县| 晋江市| 昌邑市| 循化| 林甸县| 保德县| 城步| 班戈县| 阜阳市| 霍城县| 双牌县| 屯门区| 泰安市| 松潘县| 丰台区| 柳河县| 禹州市| 务川| 莒南县| 海原县| 宁夏| 三河市| 淳安县|