歡迎光臨
每天分享高質量文章

使用 Java 註解自動化處理對應關係實現註釋程式碼化

(點選上方公眾號,可快速關註)


來源:琴水玉 ,

www.cnblogs.com/lovesqcc/p/8799187.html

概述

假設我們要從一個 ES 索引(相當於一張DB表)查詢資料,ES表有 order_no, order_type, state 等欄位, 而應用物件則有屬性 orderNo, orderType, state等。這樣,就會面臨“將應用物件的屬性與ES欄位對應起來”的問題。

固然可以透過註釋來說明,不過這樣顯得比較生硬。因為註釋並不起實際作用,程式碼裡還得寫一套對映關係,就會存在註釋與程式碼不一致的情況。 那麼,是否可以將這種對應關係的註釋用程式碼形式來解決呢? Java 註解可以解決這個問題。

實現

定義註解

首先定義註解類。註解類需要提供對應的ES欄位名 name、型別 type 以及是否必傳 required。

  • @Retention 指明註解在何時起作用,這裡是在執行時。

  • @Target 指明註解應用於何種物件,這裡應用於欄位。

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.FIELD)

@Documented

public @interface EsField {

 

  /**

   * 對應的ES欄位名

   */

  String name();

 

  /**

   * 對應的ES欄位的值型別

   * @return

   */

  String type() default “”;

 

  /**

   * 是否必傳

   */

  boolean required() default false; 

}

應用領域物件

接著,將註解應用到應用領域物件。為簡潔,應用領域物件只有四個欄位。

@Data

public class CustomerDomain implements DomainSearch {

 

  /** 店鋪ID */

  @EsField(name=”shop_id”, required = true)

  private Long shopId;

 

  /** 訂單編號 */

  @EsField(name=”order_no”)

  private String orderNo;

 

  /** 訂單狀態 */

  @EsField(name=”state”, type=”list”)

  private List state;

 

  /** 訂單型別 */

  @EsField(name=”order_type”, type=”list”)

  private List orderType;

 

}

註解解析器

接著,需要提供註解解析器,將對應的對映關係轉成ES查詢物件的一部分。

  • 使用介面的預設方法來實現,是為了支援不同的業務類自動可以轉化為ES查詢串;

  • 註解解析器需要使用Java反射機制,來獲取相應的欄位,以及欄位上的註解定義,然後根據欄位的型別、值、註解定義來做相應處理;

  • 使用反射來處理欄位時,由於欄位一般是私有的,因此必須先設定為可訪問的,處理完成後還原為不可訪問;

  • EsField field = f.getAnnotation(EsField.class) 用來獲取欄位上的註解資訊(name, type, required);Object value = f.get(customerDomain) 用來獲取欄位的值;欄位的其他型別資訊可以透過 Field 的方法拿到。

public interface DomainSearch {

 

  Log logger = LogFactory.getLog(DomainSearch.class);

 

  default String toEsQuery() {

    Object customerDomain = this;

    EsQuery esQuery = new EsQuery();

    Field[] fields = this.getClass().getDeclaredFields();

    for (Field f: fields) {

      try {

        if (Modifier.isStatic(f.getModifiers())) {

          continue;

        }

 

        f.setAccessible(true);

 

        Object value = f.get(customerDomain);

 

        if (f.getAnnotation(EsField.class) != null) {

          EsField field = f.getAnnotation(EsField.class);

 

          if (field.required() && value == null) {

            throw new RuntimeException(“field ‘” + field + “‘ is required. value is null”);

          }

 

          if (isNeedOmitted(value)) {

            f.setAccessible(false);

            continue;

          }

 

          if ((value instanceof List) && ((List)value).size() == 1) {

            // 針對 List 中單個值做最佳化查詢

            esQuery = esQuery.addTermFilter(field.name(), ((List)value).get(0));

          }

          else {

            esQuery = esQuery.addTermFilter(field.name(), value);

          }

        }

 

        f.setAccessible(false);

 

      } catch (Exception ex) {

        logger.error(“failed to build es query for field: ” + f.getName(), ex);

        throw new RuntimeException(ex.getCause());

      }

    }

    return esQuery.toJsonString();

  }

 

  /**

   * 判斷是否需要忽略該欄位的查詢

   * @param value 欄位值

   * @return 是否要忽略

   */

  default boolean isNeedOmitted(Object value) {

    if (value == null) {

      return true;

    }

 

    // 空字串搜尋值忽略

    if ((value instanceof String) && StringUtils.isBlank(value.toString())) {

      return true;

    }

 

    // 空串列串忽略

    if ((value instanceof List) && ((List)value).isEmpty()) {

      return true;

    }

    return false;

  }

}

查詢物件

ES查詢物件將所有生成的查詢條件轉化為ES可以接受的查詢字串。

public class EsQuery {

 

  private static int DEFAULT_SIZE = 100;

 

  private final Map termFilter;

  private final Map rangeFilter;

  private final Map matchFilter;

  private int size;

  private String orderBy = null;

  private String order = null;

 

  // query 查詢語法, 是否需要 filtered, filter 這兩層

  // 5.x 版本不再需要這兩層

  private boolean isNeedFilterLayer = true;

 

  private Integer from;

 

  private final Map mustNotTermFilter;

 

  private final Map shouldTermFilter;

  private Integer shouldMatchMinimum;

 

  private List includes;

  private List excludes;

 

  public EsQuery() {

    this.termFilter = new HashMap<>();

    this.rangeFilter = new HashMap();

    this.matchFilter = new HashMap();

    this.mustNotTermFilter = new HashMap<>();

    this.shouldTermFilter = new HashedMap();

    this.size = DEFAULT_SIZE;

    this.includes = new ArrayList<>();

    this.excludes = new ArrayList<>();

  }

 

  public EsQuery addTermFilter(String key, Object value) {

    this.termFilter.put(key, value);

    return this;

  }

 

  public EsQuery addMustNotTermFilter(String key, Object value) {

    this.mustNotTermFilter.put(key, value);

    return this;

  }

 

  public EsQuery addAllMustNotTermFilter(Map mustNot) {

    if (mustNot != null && !mustNot.isEmpty()) {

      this.mustNotTermFilter.putAll(mustNot);

    }

    return this;

  }

 

  public EsQuery addShouldTermFilter(String key, Object value) {

    this.shouldTermFilter.put(key, value);

    return this;

  }

 

  public EsQuery addAllShouldTermFilter(Map should) {

    if (should != null && !should.isEmpty()) {

      this.shouldTermFilter.putAll(should);

    }

    return this;

  }

 

  public EsQuery addRangeFilter(String key, long gte, long lte){

    this.rangeFilter.put(key, new Range(gte, lte));

    return this;

  }

 

  public EsQuery addMatchFilter(String key, Match value) {

    this.matchFilter.put(key, value);

    return this;

  }

 

  public EsQuery addIncludeFields(List includes) {

    this.includes.addAll(includes);

    return this;

  }

 

  public EsQuery addExcludeFields(List excludes) {

    this.excludes.addAll(excludes);

    return this;

  }

 

 

  @Override

  public String toString() {

    return toJsonString();

  }

 

  public String toJsonString() {

    Map finalQuery = new HashMap<>();

    Map queryMap = new HashMap<>();

    Map filteredMap = new HashMap<>();

    Map filterMap = new HashMap<>();

    Map boolMap = new HashMap<>();

 

    ListmustList = obtainTermFilterList(this.termFilter);

 

    ListmustNotList = obtainTermFilterList(this.mustNotTermFilter);

 

    ListshouldList = obtainTermFilterList(this.shouldTermFilter);

 

    if(!this.rangeFilter.isEmpty()){

      for(Map.Entry e: this.rangeFilter.entrySet()){

        Map rangeMap = new HashMap<>();

        Map rangeEntityMap = new HashMap<>();

        rangeEntityMap.put(e.getKey(), e.getValue().toMap());

        rangeMap.put(Constant.range, rangeEntityMap);

        mustList.add(rangeMap);

      }

    }

 

    if(!this.matchFilter.isEmpty()){

      this.matchFilter.forEach(

          (key, match) -> {

            Map matchEntityMap = new HashMap<>();

            Map matchMap = new HashMap<>();

            Map subMatchMap = new HashMap<>();

            matchEntityMap.put(Constant.query, match.getQuery());

            matchEntityMap.put(Constant.should_minum, match.getMinimumShouldMatch());

            matchMap.put(key, matchEntityMap);

            subMatchMap.put(Constant.match, matchMap);

            mustList.add(subMatchMap);

          });

    }

 

    boolMap.put(Constant.must, mustList);

    if (!mustNotList.isEmpty())

      boolMap.put(Constant.mustNot, mustNotList);

    if (!shouldList.isEmpty()) {

      // 有 minimum_should_match 不帶過濾器

      boolMap.put(Constant.should, shouldList);

      boolMap.put(Constant.should_minum, shouldMatchMinimum);

      queryMap.put(Constant.bool, boolMap);

    }

    else {

      if (isNeedFilterLayer) {

        filterMap.put(Constant.bool, boolMap);

        filteredMap.put(Constant.filter, filterMap);

        queryMap.put(Constant.filtered, filteredMap);

      }

      else {

        queryMap.put(Constant.bool, boolMap);

      }

    }

    finalQuery.put(Constant.query, queryMap);

 

    Map orderMap = new HashMap<>();

    Map orderItem = new HashMap<>();

 

    if(order != null && orderBy != null){

      orderItem.put(Constant.order, this.order);

      orderMap.put(this.orderBy, orderItem);

      finalQuery.put(Constant.sort, orderMap);

    }

 

    Map source = new HashMap<>();

    if (!includes.isEmpty()) {

      source.put(Constant.includes, this.includes);

    }

    if (!excludes.isEmpty()) {

      source.put(Constant.excludes, this.excludes);

    }

    if (!source.isEmpty()) {

      finalQuery.put(Constant.source, source);

    }

 

    finalQuery.put(Constant.size, this.size);

    if (from != null) {

      finalQuery.put(Constant.from, from.intValue());

    }

    return JSON.toJSONString(finalQuery);

  }

 

  public ListobtainTermFilterList(Map termFilter) {

    ListtermFilterList = new ArrayList<>();

    for (Map.Entry e: termFilter.entrySet()){

      Map termMap = new HashMap<>();

      Map itemMap = new HashMap<>();

      itemMap.put(e.getKey(), e.getValue());

      if(e.getValue() instanceof List){

        termMap.put(Constant.terms, itemMap);

      }else{

        termMap.put(Constant.term, itemMap);

      }

      termFilterList.add(termMap);

    }

    return termFilterList;

  }

 

  public String getOrderBy() {

    return orderBy;

  }

 

  public void setOrderBy(String orderBy) {

    this.orderBy = orderBy;

  }

 

  public String getOrder() {

    return order;

  }

 

  public void setOrder(String order) {

    this.order = order;

  }

 

  public int getSize() {

    return size;

  }

 

  public void setSize(int size) {

    this.size = size;

  }

 

  public Integer getFrom() {

    return from;

  }

 

  public void setFrom(Integer from) {

    this.from = from;

  }

 

  public Map getTermFilter() {

    return Collections.unmodifiableMap(termFilter);

  }

 

  public Map getRangeFilter() {

    return Collections.unmodifiableMap(rangeFilter);

  }

 

  public Map getMustNotTermFilter() {

    return Collections.unmodifiableMap(mustNotTermFilter);

  }

 

  public Map getShouldTermFilter() {

    return Collections.unmodifiableMap(shouldTermFilter);

  }

 

  public Map getMatchFilter() {

    return matchFilter;

  }

 

  public void setShouldMatchMinimum(Integer shouldMatchMinimum) {

    this.shouldMatchMinimum = shouldMatchMinimum;

  }

 

  public Integer getShouldMatchMinimum() {

    return shouldMatchMinimum;

  }

 

  public Map getRangeMap(String key) {

    return Collections.unmodifiableMap(rangeFilter.get(key).toMap());

  }

 

  public List getIncludes() {

    return Collections.unmodifiableList(includes);

  }

 

  public boolean isNeedFilterLayer() {

    return isNeedFilterLayer;

  }

 

  public void setNeedFilterLayer(boolean needFilterLayer) {

    isNeedFilterLayer = needFilterLayer;

  }

 

  @Override

  public boolean equals(Object o) {

    // for you to write

  }

 

  @Override

  public int hashCode() {

    // for you to write

}

小結

透過ES搜尋示例,展示瞭如何運用註解自動化處理領域物件屬性與底層ES儲存欄位之間的對應關係。實際上,如果想為應用物件或元件新增某種說明或註釋,不妨先想想是否可以透過註解自動化處理。註解亦可用於框架自動處理物件與元件的整合。Spring框架的Resource, Component, AOP,以及 Plugin 化設計思想等都是好的應用例子。

看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂