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

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

在 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
@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 实例的属性被修改时,相关的修改记录将被保存到日志记录器中了。整个过程是自动执行的,同时保证了代码的可扩展性和可维护性。

你可能感兴趣的:(Java,实用小工具,java,开发语言,spring,后端,springboot)