在大多数业务场景中,修改历史是非常重要的信息。例如,当我们对某个对象进行数据修改时,可能需要记录修改之前和修改之后的值,以便于我们对修改进行审核和追溯,以及诊断和解决潜在的问题。
在 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@Componentpublic 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;@Componentpublic 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
类中,我们可以使用以下代码来注册日志记录器:
@AutowiredDataModificationListener modificationListener;public static void main(String[] args) { SpringApplication.run(MyApplication.class, args);}@BeanCommandLineRunner init() { return args -> { modificationListener.addLogger(new ConsoleModificationLogger()); };}
在这里,我们将 DataModificationListener
对象注入到 MyApplication
类中,并在 Spring 中运行时自动初始化。通过使用 CommandLineRunner
接口,我们可以在执行完所有 Spring Boot 应用程序的启动任务之后,初始化日志记录器。
这样,当 DataModel
实例的属性被修改时,相关的修改记录将被保存到日志记录器中了。整个过程是自动执行的,同时保证了代码的可扩展性和可维护性。
来源地址:https://blog.csdn.net/qq_39035773/article/details/130967289