• Java 记录操作日志|对象修改细节


    背景描述

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

    需求分析

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

      在对象中,需要比对是否变化的属性上加上自定义注解@PropertyMsg,value设置为属性的中文描述。工具类定义如下:

    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 <T>
     */
    @Slf4j
    public class BeanChangeUtil<T> {
    
        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;
        }
    }
    

    新bean一般由前端传过来,而旧bean需要去数据库查询。自定义注解@PropertyMsg如下所示:

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

      下面创建一个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;
        }
    
    
    

    其中,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])
    

    和预期的输入结果一致。

      以上就是这篇文章的全部内容了,希望本文对道友的学习或者工作能带来一定的帮助,如有疑问请留言交流。Wiener在此祝各位生活愉快!工作顺利!

  • 相关阅读:
    SQLAlchemy 几种查询方式总结
    python的cls,self,classmethod,staticmethod
    从coding.net (git clone)项目代码到本地报403错误 解决方案
    Python中内置数据类型list,tuple,dict,set的区别和用法
    正则表达式基本语法和模式修正
    php中常用正则表达式函数
    js进阶篇之内置对象
    js进阶篇之事件响应,与网页交互
    代码自动化打包系统【原创】
    php内核探索 [转]
  • 原文地址:https://www.cnblogs.com/east7/p/15856741.html
Copyright © 2020-2023  润新知