ComponentScan
的思想.@Component
注解来说明这是一个组件,在通过ComponentScan
扫描到带有@Component
的类进行注册.一个数据库对象存在很多字段,可能全部需要比较,也可能只是部分比较.对此需要通过一定的方法找到需要比较的字段.同样使用注解进行控制.
在思考一个问题,通常我们使用关系型数据库,会存储的是一个外键(例如:1,2,3),可能不便于阅读. 这里需要将外键转换成可理解的属性.
总结:
@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 类型的开发方式, 写出如下注解.
@Import
会执行EnableDiffSelect
中的方法.scanPackages
扫描路径.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
的类
@HavingDiff
注解@DiffAnnotation
注解@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;
}
}
key: 具有`@HavingDiff`注解的类.class
value:
key: 具有`@DiffAnnotation`的字段,类的属性字段
value: `@DiffAnnotation`的实体对象
比较的时候存在前面说到的问题: 外键的可读性.
注解@DiffAnnotation
中属性outField
就是为了解决这个问题而设计.
差异化信息包装.
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
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.