java记录操作日志(对象修改细节)

背景

由于业务涉及收入敏感信息,需记录数据变更前的内容和变更后的内容,但是不能为完成任务而硬编码,要适用于不同bean。针对这种情况,本文使用泛型、反射和基于AOP的自定义注解技术来完成,对对象属性的描述通过自定义注解来完成,读取里面的属性进而记录修改历史。

需求分析

利用泛型、反射和自定义注解技术,分别比较修改前后两个Bean实例的、所有添加了自定义注解的成员变量,当值不一致时,记录变量名称和修改前后的值。 这种方法适用于处理不同的bean,可以达到一次编码,多处复用的效果。

工具类定义如下:

import com.swagger.demo.bean.ChangePropertyMsg;
import com.swagger.demo.bean.PropertyMsg;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 *
 * @param 
 */
@Slf4j
public class BeanChangeUtil<T> {

	/**
     * 传入两个相同类型的对象,对比属性得到修改信息
     * @param oldBean
     * @param newBean
     * @return 属性修改信息
     */
    public static <aClass> String getChangeInfo(Object oldBean, Object newBean){
        Class aClass = oldBean.getClass();
        BeanChangeUtil<aClass> t = new BeanChangeUtil<>();
        ChangePropertyMsg cfs = t.contrastObj(oldBean, newBean);
        if (StringUtils.isNotEmpty(cfs.getChangeMsg())) {
            return cfs.getChangeMsg();
        }
        return null;
    }
	/**
     * 传入两个相同类型的对象,对比属性得到修改信息
     * @param oldBean
     * @param newBean
     * @return **完整属性修改信息**
     */
    public ChangePropertyMsg contrastObj(Object oldBean, Object newBean) {
        // 转换为传入的泛型T
        T oldPojo = (T) oldBean;
        // 通过反射获取类型及字段属性
        Field[] fields = oldPojo.getClass().getDeclaredFields();
        return jdk8OrAfter(Arrays.asList(fields), oldPojo, (T) newBean);
    }
    // lambda表达式,表达式内部的变量都是final修饰,需要传入final类型的数组
    private ChangePropertyMsg jdk8OrAfter(List<Field> fields, T oldBean, T newBean) {
        ChangePropertyMsg cf = new ChangePropertyMsg();
        // 创建字符串拼接对象
        StringBuilder str = new StringBuilder();
        List<String> fieldList = new ArrayList<>();
        // 属性改变个数
        final int[] i = {1};
        fields.forEach(field -> {
            field.setAccessible(true);
            if (field.isAnnotationPresent(PropertyMsg.class)) {
                try {
                    // 获取属性值
                    Object newValue = field.get(newBean);
                    Object oldValue = field.get(oldBean);
                    if (ObjectUtils.notEqual(oldValue, newValue)) {
                        fieldList.add(field.getName());
                        str.append(i[0] + "、" + field.getAnnotation(PropertyMsg.class).propertyName() + ":")
                                .append("修改前->" + oldValue + ",修改后->" + newValue + "\n");
                        i[0]++;
                    }
                } catch (Exception e) {
                    log.error("比对Bean属性是否变化失败,", e);
                }
            }
        });
        cf.setChangeMsg(str.toString());
        cf.setProperties(fieldList);
        return cf;
    }
}

在对象中,需要比对是否变化的属性上加上自定义注解@PropertyMsg,propertyName设置为属性的中文描述。
新bean一般由前端传过来,而旧bean需要去数据库查询。自定义注解@PropertyMsg如下所示:

import java.lang.annotation.*;

/**
 *  属性信息注解,仅仅可以用于域声明
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface PropertyMsg {
    /**
     * 提示语,用于标记哪个字段发生变更
     *
     * @return 提示语
     */
    String value() default "";

    /**
     * 变更属性名
     * @return
     */
    String propertyName();

}

下面创建一个User case:

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

@Slf4j
public class TestChange {

    public static void main(String[] args) {
        User u1 = new User(30L, "Wiener", 27);
        User u2 = new User(30L, "楼兰胡杨", 20);
        BeanChangeUtil<User> t = new BeanChangeUtil<>();
        ChangePropertyMsg cfs = t.contrastObj(u1, u2);
        if (StringUtils.isBlank(cfs.getChangeMsg())) {
            log.info("未有改变");
        } else {
            // 属性发生变化,增加业务主键
            cfs.setBizNum(u2.getId().toString());
            log.info("属性变化:{}", cfs);
        }
    }

}
-- -----------------------------------

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.io.Serializable;

@Getter
@Setter
@ToString
public class User implements Serializable {
    //实现serializable接口
    private static final long serialVersionUID = -2241172936329900646L;
    
    private Long id;
    @PropertyMsg(propertyName = "用户姓名")
    private String userName;
    private String msg;
    @PropertyMsg(propertyName = "年龄")
    private Integer age;

    /**
     * 无参构造器
     */
    public User() {
    }

    public User(Long id, String userName, Integer age) {
        this.id = id;
        this.userName = userName;
        this.age = age;
    }


-- -----------------------------------

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.io.Serializable;

@Getter
@Setter
@ToString
public class User implements Serializable {
    //实现serializable接口
    private static final long serialVersionUID = -2241172936329900646L;

    private Long id;
    @PropertyMsg(propertyName = "用户姓名")
    private String userName;
    private String msg;
    @PropertyMsg(propertyName = "年龄")
    private Integer age;

    /**
     * 无参构造器
     */
    public User() {
    }

    public User(Long id, String userName, Integer age) {
        this.id = id;
        this.userName = userName;
        this.age = age;
    }

其中,ChangePropertyMsg用于记录属性变更结果,加到需要记录变化的字段,此注解代码如下:

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.List;

@Getter
@Setter
@ToString
public class ChangePropertyMsg {

    /**
     * 业务主键
     */
    private String bizNum;
    /**
     * 变更信息
     */
    private String changeMsg;
    /**
     * 变更属性集合
     */
    private List<String> properties;
}

执行测试用例中的main函数,在控制台输出如下信息:

20:52:10.443 [main] INFO com.swagger.demo.bean.TestChange - 属性变化:ChangePropertyMsg(changeMsg=1、用户姓名:修改前->Wiener,修改后->楼兰胡杨
2、年龄:修改前->27,修改后->20
, properties=[userName, age])

你可能感兴趣的:(java,开发语言)