需求描述
今天接到一个需求,修改数据时需要记录修改详情。详情包括,被修改的字段,修改前的值和修改后的值。
解决思路
- 分别比较修改前后两个Bean实例的所有成员变量,当值不一致时,记录变量名称,以及修改前后的值。 对于该方案,可以解决特定类型的Bean。 如果有其它类型的Bean也有这种需求,则需要新写一套逻辑,处理相应的需求。
- 上述方案不能复用,如果有多个这样的Bean需要比较,则每个Bean都需要新写一套逻辑。然而,利用泛型和反射技术,则可以达到一次编码,多处复用的效果
实现步骤
下面,就采用泛型和反射技术来实现上述需求
- 自定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ForUpdate {
String fieldName() default "";
}
- 给Bean属性添加注解
@Data
@Builder
public class Bean {
private Long id; //不需要记录变化的字段,无需添加注解 @ForUpdate
@ForUpdate(fieldName = "姓名") //需要记录变化的字段,需添加注解@ForUpdate
private String name;
@ForUpdate(fieldName = "年龄")
private Integer age;
}
- 实现记录Bean修改明细
public class BeanUtils {
/**
* 获取变更内容
* @param oldBean 更改前的Bean
* @param newBean 更改后的Bean
* @param <T>
* @return
*/
public static <T> String getChangedFields(T oldBean, T newBean){
Field[] fields = newBean.getClass().getDeclaredFields();
StringBuilder builder = new StringBuilder();
for(Field field : fields) {
field.setAccessible(true);
//skip filed without @ForUpdate
if (!field.isAnnotationPresent(ForUpdate.class)) {
continue;
}
try {
Object oldValue = field.get(oldBean);
Object newValue = field.get(newBean);
if(!Objects.equals(newValue, oldValue)) {
builder.append(field.getAnnotation(ForUpdate.class).fieldName()); //获取字段名称
builder.append(": 【更改前:");
builder.append(oldValue);
builder.append(", 更改后:");
builder.append(newValue);
builder.append("】\n");
}
} catch (Exception e) {
System.out.println(e);
}
}
return builder.toString();
}
}
- 测试
/**
* @Author tianyuxu
* @Since 2021/3/10 16:47
*/
public class BeanUtilsTest {
public static void main(String[] args) {
Bean bean1 = Bean.builder()
.id(1L)
.name("name1")
.age(10)
.build();
Bean bean2 = Bean.builder()
.id(1L)
.name("name2")
.age(20)
.build();
System.out.println(BeanUtils.getChangedFields(bean1, bean2));
//最终输出 :
// 姓名: 【更改前:name1, 更改后:name2】
// 年龄: 【更改前:10, 更改后:20】
}
}
总结
如上所述,我们利用泛型和反射实现了一个可以比较两个Bean实例的工具类。现在,如果有一个新的需求,要我们也记录Bean2这个类的实例变化,我们只需要在Bean2的成员变量上加上注解@ForUpdate, 然后直接调用工具类BeanUtils的getChangedFields()方法即可。