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 <T>
 */
@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])

  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.开始入门 3 1.Getting Started 3 2.Dependencies 依赖资源 3 2.参考资源: 4 3.它是如何工作的? 4 1.基本使用模式 4 4.单实例还是多实例(To Singleton Or Not To Singleton...)? 6 1.Singleton Model 6 2.Separate Instance 6 5. The Context 7 1.The Basics 7 2.在模板中用#foreach指令支持迭代对象 8 3.Context Chaining 9 4.模板中的己创建对象 10 5.Context对象的其它用法 10 6.Using Velocity In Servlets 11 1.Servlet Programming 11 2.Deployment 13 7. Using Velocity In General Applications 13 1.The Velocity Helper Class 14 2.Exceptions 16 3.其它细节 16 8.Application Attributes 17 9.EventCartridge and Event Handlers(事件分发和处理) 17 1.Event Handlers 17 2.Using the EventCartridge使用事件分发器 18 10.Velocity Configuration Keys and Values(配置参数名字和值说明) 20 1.Runtime Log 20 2.字符集编码问题 21 3.#foreach() Directive 21 4.#include() and #parse() Directive 21 5.资源管理 21 6.Velocimacro(宏配置) 22 7.语义更改 23 8.运行时配置 23 11.Configuring the Log System(日志记录配置) 23 1.一般的可选日志功能: 23 2.Simple Example of a Custom Logger 25 12.Configuring Resource Loaders(资源装载器配置) 26 1.Resource Loaders 26 2.Configuration Examples 27 3.插入定制资源管理器和Cache实现 29 13.Template Encoding for Internationalization(字符编码和国际化) 29 14.Velocity and XML 30 15.FAQ (Frequently Asked Questions) 32 1.Why Can't I Access Class Members and Constants from VTL? 32 2.Where does Velocity look for Templates? 33 16.Summary 33 17.Appendix 1 : Deploying the Example Servlet 33
java进行客户端的applet (小程序)开发的技术已广为使用,而用java进行服务器端的servlet(服务器小程序)开发则尚需揭开其神秘的面纱,本书正是基于这样的目的编写的。全书从java服务器的体系结构、开发工具和管理工具、编程技术、安全机制等四个方面全面介绍java服务器的升友技术。通过阅读本书,读者不仅能够知道用java服务器体系结构开发servlet与用传统cgi编写程序的好处,而且还能够掌握编写java servlet的各种技术和技巧。    本书适用于所有对java和web感兴趣的读者使用和参考。 第1部分 java服务器基础[/font] [/font] 第1章 java计算的体系结构[/font] [/font] 1.1 传统的计算模式[/font] 1.1.1 集中式计算模式[/font] 1.1.2 胖客户机/服务器计算模式[/font] 1.1.3 瘦客户机/服务器计算模式[/font] 1.1.4 java desktop与x终端[/font] 1.2 java计算[/font] 1.3 小结[/font] [/font] 第2章 java服务器与servlet[/font] [/font] 2.1 java server的结构[/font] 2.1.1 服务[/font] 2.1.2 服务器[/font] 2.1.3 服务构架[/font] 2.1.4 服务器处理构架[/font] 2.1.5 http服务[/font] .2.1.6 核心servlet[/font] 2 1.7 acl[/font] 2.2 servlet及其功能[/font] 2.2.1 什么是servlet[/font] 2.2.2 servlet与applet的区别[/font] 2.2.3 servlet的应用[/font] 2.3 servlet与cgi的比较[/font] 2.3.1 cgi[/font] 2.3.2 servlet的优点[/font] 2.4 运行servlet[/font] 2.5 小结[/font] [/font] 第2部分 java服务器的开发工具和管理工具[/font] [/font] 第3章 使用java server与servlet开发工具[/font] [/font] 3.1 java web server的安装与启动[/font] 3.1.1 在windows 95或windows nt上安装[/font] 3.1.2 在unix上安装[/font] 3.1.3 java server环境变量的设置[/font] 3.1.4 启动java server[/font] 3.1.5 在端口80上启动java server[/font] 3.1.6 结束java serve的运行[/font] 3.1.7 删除java server[/font] 3.1.8 显示缺省主页[/font] 3.1.9 使用随机文档[/font] 3.2 servlet的加载和调用[/font] 3.2.1 加载servlet[/font] 3.2.2 servlet的标识[/font] 3.2.3 调用servlet[/font] 3.3 jsdk的安装与使用[/font] 3.3.1 servletrunner[/font] 3.3.2 在netscape服务器上安装jsdk[/font] 3.3.3 在apache服务器上安装[/font] 3.4 小结[/font] [/font] 第4章 java web server的管理[/font] [/font] 4.1 进入管理工具[/font] 4.2 设置[/font] 4.2.1 设置web service[/font] 4.2.2 设置proxy service[/font] 4.2.3 设置管理服务[/font] 4.3 监视[/font] 4.3.1 记录输出[/font] 4.3.2 记录统计[/font] 4.3.3 资源的使用[/font] 4.4 安全控制[/font] 4.4.1 用户管理[/font] 4.4.2 用户组[/font] 4.4.3 访问控制表[/font] 4.4.4 资源保护[/font] 4.5 servlet管理[/font] 4.5.1 增加servlet[/font] 4.5.2 设置servlet属性[/font] 4.5.3 修改servlet属性[/font] 4.5.4 删除servlet[/font] 4.6 小结[/font] [/font] 第3部分 java服务器编程[/font] [/font] 第5章 servlet包介绍[/font] [/font] 5.1 javax.servlet包[/font] 5.1.1 接口servlet[/font] 5.1.2 接口servletconfig[/font] 5.1.3 接口servletcontext[/font] 5.1.4 接口servletrequest[/font] 5.1.5 接口servletresponse[/font] 5.1.6 类genericservlet[/font] 5.1.7 类servletinputstream[/font] 5.1.8 类servletoutputstream[/font] 5.2 javax.servlet.http包[/font] 5.2.1 接口httpservletrequest[/font] 5.2.2 接口httpservletresponse[/font] 5.2.3 类httpservlet[/font] 5.2.4 类httputils[/font] 5.3 sun.servlet包介绍[/font] 5.3.1 接口servletconnection[/font] 5.3.2 类servletloader[/font] 5.4 小结[/font] [/font] 第6章 sun.servlet.http包介绍[/font] [/font] 6.1 类cookie[/font] 6.2 类httpdate[/font] 6.3 类httpinputstream[/font] 6.4 类httpoutputstream[/font] 6.5 类httprequest[/font] 6.6 类httpresponse[/font] 6.7 类httpserver[/font] 6.8 类httpserverhandler[/font] 6.9 类httpservletconfig[/font] 6.10 类messagebytes[/font] 6.11 类messagestring[/font] 6.12 类mimeheaderfield[/font] 6.13 类mimeheaders[/font] 6.14 小结[/font] [/font] 第7章 genericservlet编程[/font] [/font] 7.1 servlet编程的一个小例子[/font] 7.1.1 genericservlet在jsdk中的地位[/font] 7.1.2 从hello world学习genericservlet编程[/font] 7.1.3 servlet的两个生命周期函数[/font] 7.2 servlet中的对象[/font] 7.2.1 servlet中的请求和应答对象[/font] 7.2.2 selvlet中的servletconfig对象[/font] 7.3 servlet输出html文件[/font] 7.4 小结[/font] [/font] 第8章 httpservlet编程介绍[/font] [/font] 8.1 http简介[/font] 8.1.1 http协议基本概念及其特点[/font] 8.1.2 http协议的请求和应答[/font] 8.1.3 http协议的信息处理方法[/font] 8.2 httpservlet编程入门[/font] 8.2.1 一个简单例子[/font] 8.2.2 httpservlet的常用对象介绍[/font] 8.3 cookie编程介绍[/font] 8.3.1 cookie简介[/font] 8.3.2 一个cookie例子程序[/font] 8.4 小结[/font] [/font] 第9章 servlet高级编程[/font] [/font] 9.1 servlet与form[/font] 9.1.1 form简介[/font] 9.1.2 一个完整的form[/font] 9.2 编写一个shtml文本[/font] 9.3 异常处理[/font] 9.4 servlet与applet的通信[/font] 9.5 小结[/font] [/font] 第10章 聊天室[/font] [/font] 10.1 “聊天室”客户程序[/font] 10.2 “聊天室”服务器程序[/font] 10.3 “聊天”[/font] 10.4 小结[/font] [/font] 第11章 rmi与servlet[/font] [/font] 11.1 rmi概述[/font] 11.2 如何编写rmi[/font] 11.2.1 定义一个远程接口[/font] 11.2.2 实现远程接口[/font] 11.2.3 写一个applet[/font] 11.2.4 写html文本[/font] 11.2.5 编译和执行[/font] 11.3 rmi与servlet[/font] 11.3.1 定义远程接口[/font] 11.3.2 实现远程接口[/font] 11.3.3 调用rmi的servlet[/font] 11.3.4 调用servlet的applet[/font] 11.3.5 三个超文本文件[/font] 11.3.6 编译和执行[/font] 11.4 小结[/font] [/font] 第12章 servlet与jdbc[/font] [/font] 12.1 jdbc简介[/font] 12.1.1 实现jdbc[/font] 12.1.2 jdbc类细节[/font] 12.2 jdbc与servlet结合[/font] 12.2.1 基本过程[/font] 12.2.2 servlet的同步[/font] 12.2.3 应用举例[/font] 12.3 小结[/font] [/font] 第13章 开发服务[/font] [/font] 13.1 服务[/font] 13.2 实现服务的核心[/font] 13.3 安装服务[/font] 13.3.1 创建属性文件[/font] 13.3.2 创建jamfile[/font] 13.4 创建日志[/font] 13.4.1 日志文件类型[/font] 13.4.2 在自己的服务中加入日志文件[/font] 13.5 服务的参数管理[/font] 13.5.1 系统参数[/font] 13.5.2 增加新的参数[/font] 13.5.3 创建存放参数的域[/font] 13.5.4 实现访问方法[/font] 13.5.5 构造器[/font] 13.5.6 update方法[/font] 13.6 使用realm来创建安全服务[/font] 13.6.1 查找realm[/font] 13.6.2 设置realm[/font] 13.6.3 收集用户数据对象[/font] 13.6.4 给服务增加身份验证代码[/font] 13.7 小结[/font] [/font] 第14章 pagecompile[/font] [/font] 14.1 生成动态主页[/font] 14.1.1 创建第一个主页[/font] 14.1.2 从html文档到java源文件[/font] 14.2 语法[/font] 14.2.1 声明变量[/font] 14.2.2 条件语句[/font] 14.2.3 循环语句[/font] 14.2.4 注释[/font] 14.2.5 使用backquotes[/font] 14.2.6 使用servlet标记[/font] 14.2.7 输出java表达式[/font] 14.3 使用java类[/font] 14.3.1 重用java类[/font] 14.3.2 创建自己的类[/font] 14.3.3 访问request和response对象[/font] 14.4 servlet初始化参数[/font] 14.5 pagecompile中使用的标记[/font] 14.6 小结[/font] [/font] 第4部分 java服务器安全机制[/font] [/font] 第15章 java web server的安全机制[/font] [/font] 15.1 安全综述[/font] 15.1.1 常见的攻击形式[/font] 15.1.2 java web server的安全机制[/font] 15.1.3 其他特点[/font] 15.1.4 unix上的独有特点[/font] 15.2 web realm机制[/font] 15.2.1 realm模型[/font] 15.2.2 用户和授权[/font] 15.2.3 用户组[/font] 15.3 访问控制表(acl)[/font] 15 3.1 访问控制表结构[/font] 15.3.2 计算权限的规则[/font] 15.3.3 例子[/font] 15.3.4 用法举例[/font] 15.4 ssl机制[/font] 15.4.1 什么是ssl[/font] 15.4.2 建立安全的web服务[/font] 15.4.3 使用认证许可[/font] 15.5 沙盒[/font] 15.6 小结[/font]
作为基于分布式文件存储的数据库,在目前的云计算实践中,MongoDB炙手可热。《深入云计算(MongoDB管理与开发实战详解)》系统全面的介绍了MongoDB开发、管理、维护和性能优化等方方面面。详细而深入,对MongoDB的开发和管理方法进行了详细的讲解,也对MongoDB的工作机制进行了深入的探讨。注重实战,通过实际中的案例为读者讲解使用MongoDB时遇到的各种问题,并给出了解决方案。本书旨在帮助云计算初学者迅速掌握MongoDB数据库,提升读者在云计算实践中的应用和开发能力。同时本书极强的系统性和大量翔实的案例对于有一定基础的中高级用户有非常好的参考价值。 第1篇 基础篇 第1章 MongoDB简介 1.1 关系型数据库简介 1.2 关系型数据库面临的问题 1.3 NoSQL的崛起 1.4 MongoDB是如何解决这些问题的 1.5 初识MongoDB 1.5.1 MongoDB的特点 1.5.2 数据模型 1.5.3 扩展性 1.5.4 功能性 1.5.5 速度快 1.5.6 简便的管理 1.5.7 使用场合 1.6 本章小结 第2章 MongoDB基本原理与安装 2.1 数据库结构 2.2 文档 2.3 集合 2.3.1 集合的无模式 2.3.2 集合的命名 2.4 MongoDB数据类型 2.4.1 基本数据类型 2.4.2 数字类型 2.4.3 日期类型 2.4.4 数组类型 2.4.5 内嵌文档类型 2.4.6 _id键和ObjectId对象 2.5 MongoDB的下载和安装 2.5.1 MongoDB的下载 2.5.2 在Windows平台下的下载与安装 2.5.3 在Linux平台下的下载与安装 2.6 MongoDB shell的使用 2.6.1 启动Shell 2.6.2 使用Shell对MongoDB的基本操作 2.6.3 使用Shell的诀窍 2.6.4 特殊的集合名 2.7 本章小结 第3章 文档的增加、修改及删除 3.1 插入并保存文档 3.1.1 插入的原理和作用 3.1.2 批量插入 3.2 删除文档 3.3 修改文档 3.3.1 整个文档的替换 3.3.2 使用修改器 3.3.3 upsert和save更新 3.3.4 修改多个文档 3.3.5 修改文档并返回修改后的文档 3.4 数据库响应 3.4.1 安全操作 3.4.2 捕获异常 3.5 客户端请求和MongoDB数据库连接 3.6 本章小结 第2篇 应用篇 第4章 查询 4.1 find简介 4.1.1 返回指定的键 4.1.2 find查询限制 4.2 条件操作符 4.2.1 $all匹配所有 4.2.2 $exists判断字段是否存在 4.2.3 null值处理 4.2.4 $mod取模运算 4.2.5 $ne不等于 4.2.6 $in包含 4.2.7 $nin不包含 4.2.8 $size数组元素个数 4.2.9 正则表达式匹配 4.2.10 Javascript 查询和$where 查询 4.2.11 count查询记录条数 4.2.12 limit限制返回记录数 4.2.13 skip 限制返回记录的起点 4.2.14 sort 排序 4.2.15 分页查询 4.2.16 随机抽取文档 4.3 distinct找出给定键所有不同的值 4.4 group分组 4.4.1 使用完成器 4.4.2 将函数作为键使用 4.5 游标 4.6 存储过程 4.7 本章小结 第5章 Capped集合 5.1 特性 5.2 使用和约束 5.3 应用 5.3.1 创建 capped collection 5.3.2 限制capped collection中对象个数 5.4 注意事项 5.5 本章小结 第6章 GridFS存储文件 6.1 为什么要用GridFS 6.2 如何实现海量存储 6.3 语言支持 6.4 简单介绍 6.5 命令行工具 6.6 内部原理 6.7 本章小结 第7章 MapReduce统计 7.1 Map函数 7.2 Reduce函数 7.3 结果存储 7.4 对Reduce函数结果进一步处理 7.5 其他控制细节 7.6 本章小结 第3篇 管理篇 第8章 管理 8.1 启动和停止MongoDB 8.1.1 使用命令行启动 8.1.2 配置文件 8.1.3 Daemon方式启动 8.1.4 mongod参数说明 8.1.5 停止数据库 8.2 访问控制 8.2.1 绑定IP内网地址访问Mongo

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值