在大多数业务场景中,修改历史是非常重要的信息。例如,当我们对某个对象进行数据修改时,可能需要记录修改之前和修改之后的值,以便于我们对修改进行审核和追溯,以及诊断和解决潜在的问题。
在 Java 开发中,我们可以使用观察者模式来实现记录 Java对 象的修改历史和实现数据修改日志。本文将介绍如何实现这一功能。
为了实现每次修改数据的时候比较修改前和修改后的记录,我们可以使用观察者模式设计一个数据修改监听器(DataModificationListener
),每次数据修改时通知监听器,并将修改前后的记录存储在日志中。对于多种类型的Java实体对象,我们可以使用注解方式,通过注解来标记需要进行日志记录的属性。
实现的基本思路如下:
DataModel
类,该类用于承载需要记录的数据模型,并提供访问和设置数据的方法。Record
类,用于保存数据的修改记录,包括被修改的属性名称、修改前的属性值和修改后的属性值。ModificationLogger
接口,定义扩展性操作(如: 获取日志记录数据)。DataModificationListener
类和 DataModificationEvent
事件类,用于监听数据修改事件并处理修改前后的记录,将数据的修改记录保存在 ModificationLogger
中。ModifiableField
,并将其应用于需要记录日志的属性上,用于标识这些属性需要记录日志。接下来,我们将逐步介绍实现这一功能的具体操作步骤。
第一步:创建 DataModel
类
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class DataModel {
private Map fields = new HashMap<>();
private List history;
public void setField(String fieldName, Object value) {
Object oldValue = fields.get(fieldName);
fields.put(fieldName, value);
// 比较修改前后的属性值,生成数据修改记录
if (oldValue != null && !oldValue.equals(value) || oldValue == null && value != null) {
Record record = new Record(fieldName, oldValue, value);
getHistory().add(record);
DataModificationEvent event = new DataModificationEvent(this, fieldName, oldValue, value);
MyApplication.getInstance().getModificationListener().notifyListeners(event);
}
}
public Object getField(String fieldName) {
return fields.get(fieldName);
}
public List getHistory() {
if (history == null) {
history = new ArrayList<>();
}
return history;
}
/**
* 获取所有需要记录日志的属性
*/
protected List getModifiableFields() {
List result = new ArrayList<>();
Field[] fields = getClass().getFields();
for (Field field : fields) {
ModifiableField annotation = field.getAnnotation(ModifiableField.class);
if (annotation != null) {
result.add(field);
}
}
return result;
}
}
在这个类中,需要记录的属性可以通过一个Map
属性来存储。在 setField
方法中,我们比较修改前后的属性值,如果值发生改变,则生成数据修改记录,并将其保存在 ModificationLogger
中。getModifiableFields
方法通过反射获取所有带有 ModifiableField
注解的属性,并返回它们的 Field
对 象列表。
第二步:创建 Record
类
public class Record {
private String fieldName;
private Object oldValue;
private Object newValue;
public Record(String fieldName, Object oldValue, Object newValue) {
this.fieldName = fieldName;
this.oldValue = oldValue;
this.newValue = newValue;
}
// 省略getter和setter方法
}
Record
类用于保存数据修改的历史记录,其中包括被修改的属性名称、修改前的属性值和修改后的属性。
第三步:创建 ModificationLogger
接口
import java.util.List;
public interface ModificationLogger {
void log(DataModel dataModel, Record record);
List getHistory(DataModel dataModel);
}
ModificationLogger
接口定义了两个扩展性方法:记录数据修改信息(log
)和获取日志记录数据(getHistory
)。实现此接口的类可以以不同的方式处理数据修改记录,如:将记录保存到文件中、将记录输出到控制台、将记录存储在数据库中等。
第四步:实现观察者模式
为了实现监听数据修改的功能,我们可以使用观察者模式。在Java中,观察者模式由一个被观察者(Observable
)和多个观察者(Observer
)组成。当被观察者状态发生变化时,所有的观察者对象都会得到通知并自动更新。
在本例中,我们可以使用 DataModificationListener
类作为被观察者,DataModificationEvent
类作为事件,ModificationLogger
接口作为观察者。当出现数据修改事件时,我们将创建一个 DataModificationEvent
的实例,并用被修改的对象、属性名称、修改前的值和修改后的值作为参数调用 DataModificationListener
实例的 notifyListeners
方法,以通知所有监听器进行记录修改信息的操作。
以下是 DataModificationListener
类和 DataModificationEvent
事件类的示例代码:
import java.util.ArrayList;
import java.util.List;
public class DataModificationListener {
private List loggers = new ArrayList<>();
public void addLogger(ModificationLogger logger) {
loggers.add(logger);
}
public void removeLogger(ModificationLogger logger) {
loggers.remove(logger);
}
public void notifyListeners(DataModificationEvent event) {
for (ModificationLogger logger : loggers) {
logger.log(event.getDataModel(), new Record(event.getFieldName(), event.getOldValue(), event.getNewValue()));
}
}
}
public class DataModificationEvent {
private DataModel dataModel;
private String fieldName;
private Object oldValue;
private Object newValue;
public DataModificationEvent(DataModel dataModel, String fieldName, Object oldValue, Object newValue) {
this.dataModel = dataModel;
this.fieldName = fieldName;
this.oldValue = oldValue;
this.newValue = newValue;
}
public DataModel getDataModel() {
return dataModel;
}
public String getFieldName() {
return fieldName;
}
public Object getOldValue() {
return oldValue;
}
public Object getNewValue() {
return newValue;
}
}
第五步:创建注解 ModifiableField
在Java语言中,注解(Annotation
)提供了在代码中添加元数据信息的方式。我们可以使用注解来记录需要记录日志的属性,以便在修改这些属性时可以方便地记录修改历史。在本例中,我们可以设计一个 ModifiableField
注解,将其应用于需要记录日志的属性上。
以下是ModifiableField
注解的示例代码:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ModifiableField {
}
使用 ModifiableField
注解的属性会被认为是需要记录日志的,然后在 DataModel
类中的 setField
方法中,通过调用 getModifiableFields
方法,获取所有需要记录日志的属性并进行修改历史记录。我们将在下面的示例代码中展示如何使用 ModifiableField
注解。
第六步:创建数据修改日志记录器
最后,我们需要实现一个数据修改日志记录器,它实现了 ModificationLogger
接口并提供了自己的实现。例如,我们可以使用控制台日志记录器将修改记录输出到控制台:
public class ConsoleModificationLogger implements ModificationLogger {
@Override
public void log(DataModel dataModel, Record record) {
System.out.printf("Entity \"%s\" [Field \"%s" changed]: %s -> %s\n", dataModel.getClass().getSimpleName(), record.getFieldName(), record.getOldValue(), record.getNewValue());
}
@Override
public List getHistory(DataModel dataModel) {
return dataModel.getHistory();
}
}
在上面的代码示例中,ConsoleModificationLogger 实现了 ModificationLogger 接口,定义了记录数据修改信息的方式,将修改历史记录输出到控制台。
最后,我们需要设置全局应用程序状态,用于将 DataModificationListener 与注册的日志记录器连接起来。在这个应用程序中,我们创建了一个 MyApplication 类,它保存了一个 DataModificationListener 对象。我们可以通过 registerModificationLogger 方法来注册一个日志记录器,也可以通过 unregisterModificationLogger 方法来取消注册。MyApplication 类的示例代码如下:
public class MyApplication {
private static MyApplication instance = new MyApplication();
private DataModificationListener modificationListener = new DataModificationListener();
private MyApplication() {
// 添加控制台日志记录器
modificationListener.addLogger(new ConsoleModificationLogger());
}
public void registerModificationLogger(ModificationLogger logger) {
modificationListener.addLogger(logger);
}
public void unregisterModificationLogger(ModificationLogger logger) {
modificationListener.removeLogger(logger);
}
public DataModificationListener getModificationListener() {
return modificationListener;
}
public static MyApplication getInstance() {
return instance;
}
}
在应用程序的启动过程中,我们需要将需要记录日志的日志记录器注册到 MyApplication
类的 modificationListener
中:
public static void main(String[] args) {
// 注册控制台日志记录器
MyApplication.getInstance().registerModificationLogger(new ConsoleModificationLogger());
// 在此处创建实体对象、修改实体对象的属性并记录修改日志
}
使用以上代码,每次实体类中带有 ModifiableField
注解的属性被修改时,相关的修改记录会被保存在日志记录器中。
将以上方法整合到Spring Boot 项目中,可以按照以下步骤操作:
第一步:添加依赖
在 pom.xml
文件中添加以下依赖:
org.springframework.boot
spring-boot-starter-aop
这个依赖主要用于Spring AOP的相关功能,以便我们可以将 DataModel
实现为一个可被代理的类,并在插入记录数据修改日志的逻辑时使用 AOP 切面。
第二步:实现 DataModel
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class DataModel {
private Map fields = new HashMap<>();
private List history;
public void setField(String fieldName, Object value) {
Object oldValue = fields.get(fieldName);
fields.put(fieldName, value);
// Only log changes for fields that have a @ModifiableField annotation
List modifiableFields = getModifiableFields();
if (modifiableFields.stream().anyMatch(f -> f.getName().equalsIgnoreCase(fieldName))) {
// Compare old and new values
if (oldValue != null && !oldValue.equals(value) || oldValue == null && value != null) {
// Add to history
Record record = new Record(fieldName, oldValue, value);
getHistory().add(record);
// Publish event
DataModificationEvent event = new DataModificationEvent(this, fieldName, oldValue, value);
MyApplication.getInstance().getModificationListener().notifyListeners(event);
}
}
}
public Object getField(String fieldName) {
return fields.get(fieldName);
}
public List getHistory() {
if (history == null) {
history = new ArrayList<>();
}
return history;
}
public List getModifiableFields() {
List result = new ArrayList<>();
Field[] fields = getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(ModifiableField.class)) {
result.add(field);
}
}
return result;
}
}
DataModel
类的实现并没有太大的变化,主要的区别在于我们在 setField()
方法中只记录带有 @ModifiableField
注解的属性。同时,我们也获取了包含基类中声明属性的所有符合条件的字段,从而弥补了基类属性无法被注解所记录日志的不足。
第三步:为 DataModel
实现代理类
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class DataModelProxy implements MethodInterceptor {
private final Object target;
private DataModelProxy(Object target) {
this.target = target;
}
public static Object create(Object target) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new DataModelProxy(target));
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = method.invoke(target, args);
return result;
}
}
这个类并不是必需的,但却是一个方便的工具类,可以用于将数据模型的创建过程移到Spring 中的某个地方并将其代理化。这可以确保所有的 DataModel
类在 Spring 中都是被代理的,从而方便地插入数据修改日志记录器的功能。
在上面的实现中,我们使用了 CGLIB 库来为 DataModel
动态生成代理类。
第四步:创建切面
切面(Aspect)是 AOP 中的一个重要概念,可以用于将一组逻辑应用于多个方法(或类)中。在这个例子中,我们需要创建一个切面,将数据修改日志记录的逻辑应用于所有 DataModel
类的实现中。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataModificationLoggingAspect {
@Autowired
private DataModificationListener modificationListener;
@AfterReturning(
value = "@annotation(com.example.demo.ModifiableField)",
returning = "returnValue"
)
public Object logModification(JoinPoint joinPoint, Object returnValue) {
DataModel dataModel = (DataModel) returnValue;
DataModel proxiedDataModel = (DataModel) DataModelProxy.create(dataModel);
return proxiedDataModel;
}
}
在上面的实现中,我们使用 @Aspect
注解来声明这是一个切面。@AfterReturning
注解定义了哪些方法需要被这个切面拦截,通过 @annotation(com.example.demo.ModifiableField)
参数指定了拦截所有使用 @ModifiableField
注解的方法。
这个方法的核心是使用 DataModelProxy
将传入的 DataModel
对象转换为代理类并返回。这样,在 DataModel
对象的属性修改时,将调用代理类的 setField
方法,并触发修改记录日志的逻辑。
第五步:注册日志记录器
最后,为DataModificationListener
类添加 @Component
注解,以便它可以被 Spring 容器自动管理:
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class DataModificationListener {
private List loggers = new ArrayList<>();
public void addLogger(ModificationLogger logger) {
loggers.add(logger);
}
public void removeLogger(ModificationLogger logger) {
loggers.remove(logger);
}
public void notifyListeners(DataModificationEvent event) {
for (ModificationLogger logger : loggers) {
logger.log(event.getDataModel(), new Record(event.getFieldName(), event.getOldValue(), event.getNewValue()));
}
}
}
这个步骤的重要性在于,DataModificationListener
类将被注入到 DataModificationLoggingAspect
类中,以便我们可以通过 @Autowired
注解来将其自动加载。
最后,在 MyApplication
类中,我们可以使用以下代码来注册日志记录器:
@Autowired
DataModificationListener modificationListener;
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
@Bean
CommandLineRunner init() {
return args -> {
modificationListener.addLogger(new ConsoleModificationLogger());
};
}
在这里,我们将 DataModificationListener
对象注入到 MyApplication
类中,并在 Spring 中运行时自动初始化。通过使用 CommandLineRunner
接口,我们可以在执行完所有 Spring Boot 应用程序的启动任务之后,初始化日志记录器。
这样,当 DataModel
实例的属性被修改时,相关的修改记录将被保存到日志记录器中了。整个过程是自动执行的,同时保证了代码的可扩展性和可维护性。