查看原文
其他

使用 Java 注解自动化处理对应关系实现注释代码化

ImportNew 2022-04-28

(点击上方公众号,可快速关注)


来源:琴水玉 ,

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<Integer> state;

 

  /** 订单类型 */

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

  private List<Integer> 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<String, Object> termFilter;

  private final Map<String, Range> rangeFilter;

  private final Map<String, Match> 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<String, Object> mustNotTermFilter;

 

  private final Map<String,Object> shouldTermFilter;

  private Integer shouldMatchMinimum;

 

  private List<String> includes;

  private List<String> 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<String,Object> 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<String,Object> 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<String> includes) {

    this.includes.addAll(includes);

    return this;

  }

 

  public EsQuery addExcludeFields(List<String> excludes) {

    this.excludes.addAll(excludes);

    return this;

  }

 

 

  @Override

  public String toString() {

    return toJsonString();

  }

 

  public String toJsonString() {

    Map<String, Object> finalQuery = new HashMap<>();

    Map<String, Object> queryMap = new HashMap<>();

    Map<String, Object> filteredMap = new HashMap<>();

    Map<String, Object> filterMap = new HashMap<>();

    Map<String, Object> boolMap = new HashMap<>();

 

    List<Object> mustList = obtainTermFilterList(this.termFilter);

 

    List<Object> mustNotList = obtainTermFilterList(this.mustNotTermFilter);

 

    List<Object> shouldList = obtainTermFilterList(this.shouldTermFilter);

 

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

      for(Map.Entry<String, Range> e: this.rangeFilter.entrySet()){

        Map<String, Object> rangeMap = new HashMap<>();

        Map<String, Object> 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<String, String> matchEntityMap = new HashMap<>();

            Map<String, Map> matchMap = new HashMap<>();

            Map<String, 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<String, Object> orderMap = new HashMap<>();

    Map<String, Object> 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<String,Object> 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 List<Object> obtainTermFilterList(Map<String, Object> termFilter) {

    List<Object> termFilterList = new ArrayList<>();

    for (Map.Entry<String, Object> e: termFilter.entrySet()){

      Map<String, Object> termMap = new HashMap<>();

      Map<String, Object> 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<String, Object> getTermFilter() {

    return Collections.unmodifiableMap(termFilter);

  }

 

  public Map<String, Range> getRangeFilter() {

    return Collections.unmodifiableMap(rangeFilter);

  }

 

  public Map<String, Object> getMustNotTermFilter() {

    return Collections.unmodifiableMap(mustNotTermFilter);

  }

 

  public Map<String, Object> getShouldTermFilter() {

    return Collections.unmodifiableMap(shouldTermFilter);

  }

 

  public Map<String, Match> getMatchFilter() {

    return matchFilter;

  }

 

  public void setShouldMatchMinimum(Integer shouldMatchMinimum) {

    this.shouldMatchMinimum = shouldMatchMinimum;

  }

 

  public Integer getShouldMatchMinimum() {

    return shouldMatchMinimum;

  }

 

  public Map<String, Object> getRangeMap(String key) {

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

  }

 

  public List<String> 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技能

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存