乐趣区

关于java:对象比较

对象比拟

命题

  • 对数据库对象在更新的时候进行数据比拟, 记录差别.

设计

确定比拟对象

  • 在这里应用 Spring 中 ComponentScan 的思维.
    在 Spring 中通过 @Component 注解来阐明这是一个组件, 在通过 ComponentScan 扫描到带有 @Component 的类进行注册.

确定比拟的字段

  • 一个数据库对象存在很多字段, 可能全副须要比拟, 也可能只是局部比拟. 对此须要通过肯定的办法找到须要比拟的字段. 同样应用注解进行管制.
  • 在思考一个问题, 通常咱们应用关系型数据库, 会存储的是一个外键(例如:1,2,3), 可能不便于浏览. 这里须要将外键转换成可了解的属性.
  • 总结:

    1. 单纯值比拟
    2. 外键的值比拟, 可读性

实现

  • 设计注解 @HavingDiff 这个注解的目标是给实体对象应用, 用来示意这是一个须要比拟的对象
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface HavingDiff {}
  • 设计注解 @DiffAnnotation 这个注解的目标是给字段 (属性) 应用
@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;
}
  • 注解和注解的实体对象曾经设计实现, 接下来就须要进行包扫描门路的获取了.
  • 应用 Spring 的 enable 类型的开发方式, 写出如下注解.

    • 注解解释

      1. @Import 会执行 EnableDiffSelect 中的办法.
      2. scanPackages 扫描门路.
      3. byIdMethod mapper 的依据 id 查询方法名.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(value = {EnableDiffSelect.class})
public @interface EnableDiff {String[] scanPackages() default {};

  String byIdMethod() default "selectById";}
  • EnableDiffSelect 实现 ImportSelector 接口, 在这里的目标是获取注解 EnableDiff 的两个属性值, 放入线程变量, 在后续提供扫描入口
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];
  }
}
  • 须要扫描的包门路曾经胜利获取, 接下来就是扫描具备 @HavingDiff 的类

    • 上面代码的思维

      1. 判断这个类是否存有 @HavingDiff 注解
      2. 存在后持续判断字段是否由 @DiffAnnotation 注解
      3. 组装对象放入 map 对象
        key: 具备@HavingDiff 注解的类.class
        value:

        key: 具备 `@DiffAnnotation` 的字段, 类的属性字段
        value: `@DiffAnnotation` 的实体对象
        
@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

退出移动版