文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何记录Java对象的修改历史和实现数据修改日志

2023-09-16 12:18

关注

在大多数业务场景中,修改历史是非常重要的信息。例如,当我们对某个对象进行数据修改时,可能需要记录修改之前和修改之后的值,以便于我们对修改进行审核和追溯,以及诊断和解决潜在的问题。

在 Java 开发中,我们可以使用观察者模式来实现记录 Java对 象的修改历史和实现数据修改日志。本文将介绍如何实现这一功能。

基本思路

为了实现每次修改数据的时候比较修改前和修改后的记录,我们可以使用观察者模式设计一个数据修改监听器(DataModificationListener),每次数据修改时通知监听器,并将修改前后的记录存储在日志中。对于多种类型的Java实体对象,我们可以使用注解方式,通过注解来标记需要进行日志记录的属性。

实现的基本思路如下:

  1. 创建一个抽象的 DataModel类,该类用于承载需要记录的数据模型,并提供访问和设置数据的方法。
  2. 创建 Record类,用于保存数据的修改记录,包括被修改的属性名称、修改前的属性值和修改后的属性值。
  3. 创建 ModificationLogger接口,定义扩展性操作(如: 获取日志记录数据)。
  4. 实现观察者模式,创建 DataModificationListener类和 DataModificationEvent事件类,用于监听数据修改事件并处理修改前后的记录,将数据的修改记录保存在 ModificationLogger中。
  5. 创建注解 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

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯