Java 对象比较-拓展

2020-08-28 08:09:40 +08:00
 huifer

对象比较

命题

设计

确定比较对象

确定比较的字段

实现

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface HavingDiff {

}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface DiffAnnotation {


  /**
   * 字段中文名称
   */
  String name() default "";

  /**
   * 消息,在这里使用[old]和[new]进行替换
   */
  String msg() default "";

  /**
   * mapper class
   */
  Class<?> mapper() default Object.class;

  /**
   * 链接对象
   */
  Class<?> outJoin() default Object.class;


  /**
   * 外联对象需要显示的字符串属性,用来展示的连接字段
   */
  String outField() default "";


}
public class DiffAnnotationEntity {

  String name;

  String msg;

  Class<?> mapper;

  Class<?> outJoin;
  String outField;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(value = {EnableDiffSelect.class})
public @interface EnableDiff {

  String[] scanPackages() default {};

  String byIdMethod() default "selectById";
}

public class EnableDiffSelect implements ImportSelector {

  @Override
  public String[] selectImports(
      AnnotationMetadata annotationMetadata) {

    Map<String, Object> annotationAttributes = annotationMetadata
        .getAnnotationAttributes(EnableDiff.class.getName());

    String[] scanPackages = (String[]) annotationAttributes.get("scanPackages");
    String byIdSQL = (String) annotationAttributes.get("byIdMethod");
    DiffThreadLocalHelper.setScan(scanPackages);
    DiffThreadLocalHelper.setByIdMethod(byIdSQL);
    return new String[0];
  }
}
@Component
public class DiffRunner implements CommandLineRunner, Ordered {


  /**
   * key: 类的字节码 value: Map -> key: 字段,value: 字段上面的注解对象
   */
  static Map<Class<?>, Map<String, DiffAnnotationEntity>> cache = new HashMap<>();

  public static Map<String, DiffAnnotationEntity> get(Class<?> clazz) {
    return cache.get(clazz);
  }

  @Override
  public void run(String... args) throws Exception {
    List<String> scan = DiffThreadLocalHelper.getScan();

    for (String packageStr : scan) {
      if (!StringUtils.isEmpty(packageStr)) {
        Set<Class<?>> classes = ScanUtils.getClasses(packageStr);
        for (Class<?> aClass : classes) {

          Map<String, DiffAnnotationEntity> diffEntityMap = clazzWork(aClass);
          if (!CollectionUtils.isEmpty(diffEntityMap)) {

            cache.put(aClass, diffEntityMap);
          }
        }
      }
    }
  }

  private Map<String, DiffAnnotationEntity> clazzWork(Class<?> clazz) {
    HavingDiff havingDiff = clazz.getAnnotation(HavingDiff.class);
    // 是否存在这个注解, 如果存在则进行
    Map<String, DiffAnnotationEntity> map = new HashMap<>();
    if (havingDiff != null) {

      for (Field declaredField : clazz.getDeclaredFields()) {
        declaredField.setAccessible(true);
        // 字段名称
        String fieldName = declaredField.getName();
        // 获取注解
        DiffAnnotation diffAnnotation = declaredField.getAnnotation(DiffAnnotation.class);
        if (diffAnnotation != null) {
          DiffAnnotationEntity diffAnnotationEntity = annToEntity(diffAnnotation);
          map.put(fieldName, diffAnnotationEntity);
        }
      }
    }
    return map;
  }


  /**
   * 注解转换成为实体对象
   */
  private DiffAnnotationEntity annToEntity(DiffAnnotation diffAnnotation) {

    DiffAnnotationEntity diffAnnotationEntity = new DiffAnnotationEntity();
    diffAnnotationEntity.setName(diffAnnotation.name());
    diffAnnotationEntity.setMsg(diffAnnotation.msg());
    diffAnnotationEntity.setMapper(diffAnnotation.mapper());
    diffAnnotationEntity.setOutJoin(diffAnnotation.outJoin());
    diffAnnotationEntity.setOutField(diffAnnotation.outField());

    return diffAnnotationEntity;

  }

  @Override
  public int getOrder() {
    return Ordered.LOWEST_PRECEDENCE;
  }
}
  1. 比较对象是相同的类 这里直接通过一个泛型 T 解决
  2. 获取新老字段属性值,比较字段 反射遍历字段,获取新老字段属性值
  3. 通过字段名称从上一步的得到的 map 中获取注解信息
key: 具有`@HavingDiff`注解的类.class
value: 
    key: 具有`@DiffAnnotation`的字段,类的属性字段
    value: `@DiffAnnotation`的实体对象

  1. 比较的时候存在前面说到的问题: 外键的可读性. 注解@DiffAnnotation中属性outField就是为了解决这个问题而设计.

    • 在可读性之前还需要做一个事情: 查询数据库(根据 id 查询)得到外联的实体对象
      • 得到实体对象后就可以通过反射来获取属性值了.
  2. 差异化信息包装.

public class DiffInfoEntity {

  private String field;
  private String msg;
  private String txId;
  private String ov;
  private String nv;
}
@Service
public class IDiffInterfaceImpl<T> implements IDiffInterface<T> {

  private static final String OLD_PLACEHOLDER = "old";
  private static final String NEW_PLACEHOLDER = "new";
  Gson gson = new Gson();
  @Autowired
  private ApplicationContext context;
  @Autowired
  private SqlSession sqlSession;

  /**
   * @param source 原始对象
   * @param target 修改后的对象
   */
  @Override
  public List<DiffInfoEntity> diff(T source, T target, String logTxId) {

    Class<?> sourceClass = source.getClass();
    List<DiffInfoEntity> res = new ArrayList<>();
    for (Field declaredField : sourceClass.getDeclaredFields()) {
      declaredField.setAccessible(true);
      // 字段名称
      String fieldName = declaredField.getName();

      String oldValue = getTargetValue(source, fieldName);
      String newValue = getTargetValue(target, fieldName);

      // 注解对象
      DiffAnnotationEntity fromFiled = getFromFiled(source, fieldName);
      if (fromFiled != null) {

        // 字段中文
        String nameCn = fromFiled.getName();

        // 外联对象的取值字段
        String outField = fromFiled.getOutField();
        // 外联对象的字节码
        Class<?> outJoin = fromFiled.getOutJoin();
        // 外联对象的 mapper
        Class<?> mapper = fromFiled.getMapper();

        // 三个值都是默认值则不做外联查询
        if (StringUtils.isEmpty(outField) &&
            outJoin.equals(Object.class) &&
            mapper.equals(Object.class)
        ) {
          if (oldValue.equals(newValue)) {

            String changeLog = changeData(oldValue, newValue, fromFiled.getMsg());
            DiffInfoEntity diffInfoEntity = genDiffInfoEntity(logTxId, nameCn, oldValue, newValue,
                                                              changeLog);
            res.add(diffInfoEntity);

          }
        } else {
          String ov = mapper(mapper, oldValue, outField);
          String nv = mapper(mapper, newValue, outField);
          if (ov.equals(nv)) {

            String changeLog = changeData(ov, nv, fromFiled.getMsg());
            DiffInfoEntity diffInfoEntity = genDiffInfoEntity(logTxId, nameCn, ov, nv, changeLog);
            res.add(diffInfoEntity);
          }
        }
      }
    }
    return res;


  }

  private DiffInfoEntity genDiffInfoEntity(String logTxId, String nameCn, String ov, String nv,
                                           String changeLog) {
    DiffInfoEntity diffInfoEntity = new DiffInfoEntity();
    diffInfoEntity.setField(nameCn);
    diffInfoEntity.setMsg(changeLog);
    diffInfoEntity.setNv(nv);
    diffInfoEntity.setOv(ov);
    diffInfoEntity.setTxId(logTxId);
    return diffInfoEntity;
  }


  private String mapper(Class<?> mapper, Serializable serializable, String filed) {
    try {
      Class<?> aClass = Class.forName(mapper.getName());
      Object mapperObj = Proxy.newProxyInstance(aClass.getClassLoader(),
                                                new Class[]{mapper},
                                                new Target(sqlSession.getMapper(mapper))
      );
      Method selectById = mapperObj.getClass()
          .getMethod(DiffThreadLocalHelper.getIdMethod(), Serializable.class);
      Object invoke = selectById.invoke(mapperObj, serializable);
      return getValue(invoke, filed, "");
    } catch (Exception e) {
      e.printStackTrace();
    }
    return "";
  }

  /**
   * 获取变更的文字内容
   */
  private String changeData(String oldValue, String newValue, String msg) {
    return msg.replace(OLD_PLACEHOLDER, oldValue).replace(NEW_PLACEHOLDER, newValue);
  }

  private String getTargetValue(T t, String field) {
    String result = "";
    result = getValue(t, field, result);

    return result;
  }

  private String getValue(Object t, String field, String result) {
    Class<?> aClass = t.getClass();
    for (Field declaredField : aClass.getDeclaredFields()) {

      declaredField.setAccessible(true);

      String fieldName = declaredField.getName();
      if (field.equals(fieldName)) {
        try {
          Object o = declaredField.get(t);
          result = String.valueOf(o);
        } catch (IllegalAccessException e) {
          e.printStackTrace();
        }
      }
    }
    return result;
  }

  /**
   * 根据类型获取注解的实体对象
   * <p>
   * key:字段,value:对象
   *
   * @see DiffAnnotationEntity
   */
  private Map<String, DiffAnnotationEntity> getFromClazz(T t) {
    return DiffRunner.get(t.getClass());
  }

  private DiffAnnotationEntity getFromFiled(T t, String field) {
    return getFromClazz(t).get(field);
  }

  private static class Target implements InvocationHandler {

    private final Object target;

    public Target(Object target) {
      this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      return method.invoke(target, args);
    }
  }


}

项目地址: https://github.com/huifer/crud 分支: dev

1842 次点击
所在节点    Java
2 条回复
6666666666666666
2020-08-28 17:45:40 +08:00
想知道使用场景?
huifer
2020-08-29 08:17:34 +08:00
@6666666666666666 应用场景比较单一,如: 在修改表对象的时候可以进行一次比较,用来记录修改前后的数据差异 (便于阅读的差异描述)。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/702068

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX