自定义注解利用反射记录日志

场景:
项目中有一个供应商模块,包含供应商的基础信息、供应商资质等,字段比较多,其中有些信息比较重要,如果修改了需要记录修改日志(如哪个信息从什么改为什么),同时还需要重新提交,而有些信息修改了值需要记下日志,并不需要重新审核,还有一些无关紧要的信息修改了无需记录日志,也不需要审核

最开始项目中记录日志只针对三四个关键字段,比较简单,直接用待更新的base获取指定字段值与数据库中这个字段的原始值比较,如:
StringBuffer sb = new StringBuffer("");
if(updateBean.getPropertiesA().equals(oldBean.getPropertiesA())){
//拼接更改信息
sb.append(“XXX 由 oldBean.getPropertiesA()---->updateBean.getPropertiesA()).append(”;");
}

if(updateBean.getPropertiesB().equals(oldBean.getPropertiesB())){
//拼接更改信息
sb.append(“XXX 由 oldBean.getPropertiesB()---->updateBean.getPropertiesB()).append(”;");
}

最后将拼接的更改信息作为日志的content保存数据库日志表中

如果别的字段也需要记日志,当然也可以直接在后面继续加if判断,但是当需要记录日志的字段变多,比如二十个,三十个,继续加if感觉很烦,就想了下能不能通过反射去get值,后来找了下反射获取字段值的文章看了下

我这个还需要区分哪些字段需要记日志,哪些不需要记日志,反射获取值的时候只获取需要记日志的,结合之前的经验,可以用注解,对自己关心的字段加上自定义的注解。

部分代码如下:

package com.lyc.util;

import java.lang.annotation.*;

/**
 * Created by liyanchun on 2019/11/15.
 * 自定义的注解
 */
@Documented
@Target(ElementType.FIELD)
@Inherited
@Retention(RetentionPolicy.RUNTIME )
public @interface MyLogAnnotation {

    /**字段的中文名称*/
    String paramName();

    /**是否需要重新提交审核*/
    boolean isNeedExamine() default true;

    /**是否需要记日志*/
    boolean isNeedLog() default true;
	
	/**后面还可以加自己需要的配置*/

}
package com.lyc.bean;

import com.lyc.util.MyLogAnnotation;

import java.io.Serializable;
import java.util.Date;

/**
 * Created by liyanchun on 2019/11/15.
 *
 * 用户实体测试
 */
public class UserBase implements Serializable {
    private static final long serialVersionUID = -4831337074413975246L;

    @MyLogAnnotation(paramName = "姓名")
    private String userName;

    @MyLogAnnotation(paramName = "年龄")
    private Integer age;

    @MyLogAnnotation(paramName = "生日")
    private Date birthDay;

    @MyLogAnnotation(paramName = "身高",isNeedExamine = false)
    private Double height;

    /**体重 这个保密变化就不做记录也不通知任何人了^_^ */
    private Double weight;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getBirthDay() {
        return birthDay;
    }

    public void setBirthDay(Date birthDay) {
        this.birthDay = birthDay;
    }

    public Double getHeight() {
        return height;
    }

    public void setHeight(Double height) {
        this.height = height;
    }

    public Double getWeight() {
        return weight;
    }

    public void setWeight(Double weight) {
        this.weight = weight;
    }
}




package com.lyc.bean;

import com.lyc.util.MyLogAnnotation;

import java.io.Serializable;

/**
 * Created by liyanchun on 2019/11/15.
 *
 * 班级实体测试
 */
public class ClassBase implements Serializable {
    private static final long serialVersionUID = 874547538509067967L;


    @MyLogAnnotation(paramName = "班级编号")
    private String classNo;

    @MyLogAnnotation(paramName = "班级学生总数",isNeedExamine = false)
    private Integer studentNum;

    @MyLogAnnotation(paramName = "班主任")
    private String classTeacher;


    public String getClassNo() {
        return classNo;
    }

    public void setClassNo(String classNo) {
        this.classNo = classNo;
    }

    public Integer getStudentNum() {
        return studentNum;
    }

    public void setStudentNum(Integer studentNum) {
        this.studentNum = studentNum;
    }

    public String getClassTeacher() {
        return classTeacher;
    }

    public void setClassTeacher(String classTeacher) {
        this.classTeacher = classTeacher;
    }
}
package com.lyc.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Created by liyanchun on 2019/11/15.
 * 反射工具类
 */
public class ReflectUtil {

    /**
     * 获取对象属性,返回一个字符串数组
     *
     * @param  o 对象
     * @return String[] 字符串数组
     */
    private String[] getFiledName(Object o){
        try{
            Field[] fields = o.getClass().getDeclaredFields();
            String[] fieldNames = new String[fields.length];
            for (int i=0; i < fields.length; i++)
            {
                fieldNames[i] = fields[i].getName();
            }
            return fieldNames;
        } catch (SecurityException e){
            e.printStackTrace();
            System.out.println(e.toString());
        }
        return null;
    }


    /**
     * 使用反射根据属性名称获取属性值
     *
     * @param  fieldName 属性名称
     * @param  o 操作对象
     * @return Object 属性值
     */
    public static Object getFieldValueByName(String fieldName, Object o){
        try{
            String firstLetter = fieldName.substring(0, 1).toUpperCase();
            String getter = "get" + firstLetter + fieldName.substring(1);
            Method method = o.getClass().getMethod(getter, new Class[] {});
            Object value = method.invoke(o, new Object[] {});
            return value;
        } catch (Exception e){
            System.out.println("属性不存在");
            return null;
        }
    }
}
package com.lyc.bean;

import java.io.Serializable;

/**
 * Created by liyanchun on 2019/11/15.
 * 日志信息实体
 */
public class TestLogVo implements Serializable{
    private static final long serialVersionUID = -4378093647208529734L;
	
	/**更改的字段名*/
    private String paramName;
    /**更改前的值*/
    private String fmParam;
	/**更改后的值*/
    private String toParam;

    public String getParamName() {
        return paramName;
    }

    public void setParamName(String paramName) {
        this.paramName = paramName;
    }

    public String getFmParam() {
        return fmParam;
    }

    public void setFmParam(String fmParam) {
        this.fmParam = fmParam;
    }

    public String getToParam() {
        return toParam;
    }

    public void setToParam(String toParam) {
        this.toParam = toParam;
    }
}

上面都准备好了,开始写实现逻辑:

package com.lyc.util;

import com.lyc.bean.TestLogVo;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * Created by liyanchun on 2019/11/15.
 * 构建日志信息的工具类
 */
public class LogUtil {

    public static Boolean buildLogByReflect(Object oldBase,Object newBase,List<TestLogVo> updateParamLogList){
        Boolean isNeedReSubmitExamine = Boolean.FALSE;
        List<Field> list = Arrays.asList(oldBase.getClass().getDeclaredFields());
        if(ListUtil.isNullOrEmpty(list)){
            return isNeedReSubmitExamine;
        }
        for(Field field : list){
            if(!field.isAnnotationPresent(MyLogAnnotation.class)){//是否使用了自定义的MyLogAnnotation注解
                continue;
            }
            for (Annotation anno : field.getDeclaredAnnotations()) {//获得所有的注解
                if(!anno.annotationType().equals(MyLogAnnotation.class) ){//找到自己的注解
                    continue;
                }
                if (((MyLogAnnotation)anno).isNeedLog()){//注解的值 是否需要记录日志

                    Object oldValue = ReflectUtil.getFieldValueByName(field.getName(), oldBase);
                    Object newValue = ReflectUtil.getFieldValueByName(field.getName(), newBase);
                    String paramName = ((MyLogAnnotation)anno).paramName();

                    //判断是否更改了值  如果更改了则记录日志
                    TestLogVo logVo = compareValue(oldValue,newValue,field,paramName);
                    if (null == logVo){//没有值更改
                        continue;
                    }
                    updateParamLogList.add(logVo);
                    if(((MyLogAnnotation)anno).isNeedExamine()){//注解的值 是否需要重新提交审核
                        isNeedReSubmitExamine = Boolean.TRUE;
                    }
                }
            }
        }
        return isNeedReSubmitExamine;
    }


    public static TestLogVo compareValue(Object oldValue,Object newValue,Field field,String paramName){
        // 如果类型是String
        if (field.getGenericType().toString().equals("class java.lang.String")) { // 如果type是类类型,则前面包含"class ",后面跟类名
            String val1 = (String) oldValue;
            String val2 = (String) newValue;
            if (StringUtil.isNullOrBlank(val1) && !StringUtil.isNullOrBlank(val2)){//空改成有值
                return buildLogVo(paramName,"空",val2);
            } else if (!StringUtil.isNullOrBlank(val1) && StringUtil.isNullOrBlank(val2)){//有值改成空
                return buildLogVo(paramName,val1,"空");
            } else if (!StringUtil.isNullOrBlank(val1) && !StringUtil.isNullOrBlank(val2) && !val1.equals(val2)){//有值改成其他值
                return buildLogVo(paramName,val1,val2);
            }
        }

        // 如果类型是Integer
        if (field.getGenericType().toString().equals("class java.lang.Integer")) {
            Integer val1 = (Integer) oldValue;
            Integer val2 = (Integer) newValue;
            if (null == val1 && null != val2){
                return buildLogVo(paramName,"空",val2.toString());
            } else if (null != val1 && null == val2){
                return buildLogVo(paramName,val1.toString(),"空");
            } else if (null != val1 && null != val2 && !val1.equals(val2)){
                return buildLogVo(paramName,val1.toString(),val2.toString());
            }
        }

        // 如果类型是Double
        if (field.getGenericType().toString().equals("class java.lang.Double")) {
            Double val1 = (Double) oldValue;
            Double val2 = (Double) newValue;
            if (null == val1 && null != val2){
                return buildLogVo(paramName,"空",val2.toString());
            } else if (null != val1 && null == val2){
                return buildLogVo(paramName,val1.toString(),"空");
            } else if (null != val1 && null != val2 && !val1.equals(val2)){
                return buildLogVo(paramName,val1.toString(),val2.toString());
            }
        }

        // 如果类型是Boolean 是封装类
        if (field.getGenericType().toString().equals("class java.lang.Boolean")) {
            Boolean val1 = (Boolean) oldValue;
            Boolean val2 = (Boolean) newValue;
            if (null == val1 && null != val2){
                return buildLogVo(paramName,"空",val2.toString());
            } else if (null != val1 && null == val2){
                return buildLogVo(paramName,val1.toString(),"空");
            } else if (null != val1 && null != val2 && !val1.equals(val2)){
                return buildLogVo(paramName,val1.toString(),val2.toString());
            }
        }

        // 如果类型是boolean 基本数据类型不一样 这里有点说明如果定义名是 isXXX的 那就全都是isXXX的
        // 反射找不到getter的具体名
//        if (field.getGenericType().toString().equals("boolean")) {
//            Method m = (Method) object.getClass().getMethod(
//                    field.getName());
//            Boolean val = (Boolean) m.invoke(object);
//            if (val != null) {
//                System.out.println("boolean type:" + val);
//            }
//        }

        // 如果类型是Date
        if (field.getGenericType().toString().equals("class java.util.Date")) {
            Date val1 = (Date) oldValue;
            Date val2 = (Date) newValue;
            if (null == val1 && null != val2){
                return buildLogVo(paramName,"空", DateUtil.dateFormatToString(val2, DateUtil.ISO_EXPANDED_DATE_FORMAT));
            } else if (null != val1 && null == val2){
                return buildLogVo(paramName,DateUtil.dateFormatToString(val1,DateUtil.ISO_EXPANDED_DATE_FORMAT),"空");
            } else if (null != val1 && null != val2 && !val1.equals(val2)){
                return buildLogVo(paramName,DateUtil.dateFormatToString(val1,DateUtil.ISO_EXPANDED_DATE_FORMAT),DateUtil.dateFormatToString(val2,DateUtil.ISO_EXPANDED_DATE_FORMAT));
            }
        }
        // 如果还需要其他的类型请自己做扩展
        return null;
    }

    private static TestLogVo buildLogVo(String paramName,String fmParam,String toParam){
        TestLogVo logVo = new TestLogVo();
        logVo.setParamName(paramName);
        logVo.setFmParam(fmParam);
        logVo.setToParam(toParam);
        return logVo;
    }
}

最后就是测试了,分别创建了多个UserBase模拟同一个实体类的不同修改,然后又创建了多个ClassBase模拟日志工具类可以适用不同的实体类

package com.lyc.test;

import com.lyc.bean.ClassBase;
import com.lyc.bean.TestLogVo;
import com.lyc.bean.UserBase;
import com.lyc.util.DateUtil;
import com.lyc.util.ListUtil;
import com.lyc.util.LogUtil;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by liyanchun on 2019/11/15.
 */
public class TestReflectLog {

    public static void main(String[] args){

        testUserLog();

        System.out.println();
        System.out.println();
        System.out.println();

//        testClassLog();
    }


    public static void testUserLog(){

        UserBase userBase = new UserBase();
        userBase.setUserName("张三");
        userBase.setAge(23);
        userBase.setBirthDay(DateUtil.strFormatToDate("1992-02-23",DateUtil.SIMPLE_FORMAT));
        userBase.setHeight(1.23);
        userBase.setWeight(123d);

        //测试 只改体重  不记日志 不审核
        UserBase userBase2 = new UserBase();
        userBase2.setUserName("张三");
        userBase2.setAge(23);
        userBase2.setBirthDay(DateUtil.strFormatToDate("1992-02-23",DateUtil.SIMPLE_FORMAT));
        userBase2.setHeight(1.23);
        userBase2.setWeight(222d);


        //测试  改身高、体重 记一条日志 不审核
        UserBase userBase3 = new UserBase();
        userBase3.setUserName("张三");
        userBase3.setAge(23);
        userBase3.setBirthDay(DateUtil.strFormatToDate("1992-02-23",DateUtil.SIMPLE_FORMAT));
        userBase3.setHeight(1.32);
        userBase3.setWeight(333d);

        //测试 改所有 记四条日志 需要审核
        UserBase userBase4 = new UserBase();
        userBase4.setUserName("李四");
        userBase4.setAge(64);
        userBase4.setBirthDay(DateUtil.strFormatToDate("1992-06-04",DateUtil.SIMPLE_FORMAT));
        userBase4.setHeight(1.64);
        userBase4.setWeight(164d);

        //测试 空值
        UserBase userBase5 = new UserBase();


        List<TestLogVo> testLogVoList = new ArrayList<TestLogVo>();
        //第一种情况
        Boolean result = LogUtil.buildLogByReflect(userBase,userBase2,testLogVoList);
        //第二种情况
//        Boolean result = LogUtil.buildLogByReflect(userBase,userBase3,testLogVoList);
		//第三种情况
//        Boolean result = LogUtil.buildLogByReflect(userBase,userBase4,testLogVoList);
		//第四种情况
//        Boolean result = LogUtil.buildLogByReflect(userBase,userBase5,testLogVoList);
		//第五种情况
//        Boolean result = LogUtil.buildLogByReflect(userBase5,userBase,testLogVoList);


        System.out.println("更改用户信息是否需要审核result:" + result);
        System.out.println("更改用户信息生成的日志数量:" + testLogVoList.size());
        if (ListUtil.isNotEmpty(testLogVoList)){
            for (TestLogVo logVo : testLogVoList){
                System.out.println("【" + logVo.getParamName() + "】从" + logVo.getFmParam() + "--->" + logVo.getToParam());
            }
        }
    }


    public static void testClassLog(){

        ClassBase classBase = new ClassBase();
        classBase.setClassNo("00001");
        classBase.setStudentNum(10);
        classBase.setClassTeacher("一班主任");

        //测试 只改班级学生总数 记一条日志 不需要审核
        ClassBase classBase2 = new ClassBase();
        classBase2.setClassNo("00001");
        classBase2.setStudentNum(11);
        classBase2.setClassTeacher("一班主任");

        //测试 更改所有字段 记三条日志 需要审核
        ClassBase classBase3 = new ClassBase();
        classBase3.setClassNo("00002");
        classBase3.setStudentNum(20);
        classBase3.setClassTeacher("二班主任");


        List<TestLogVo> testLogVoList = new ArrayList<TestLogVo>();
        //第一种情况
//        Boolean result = LogUtil.buildLogByReflect(classBase,classBase2,testLogVoList);
		//第二种情况
//        Boolean result = LogUtil.buildLogByReflect(classBase,classBase3,testLogVoList);
        System.out.println("更改班级信息是否需要审核result:" + result);
        System.out.println("更改班级信息生成的日志数量:" + testLogVoList.size());
        if (ListUtil.isNotEmpty(testLogVoList)){
            for (TestLogVo logVo : testLogVoList){
                System.out.println("【" + logVo.getParamName() + "】从" + logVo.getFmParam() + "--->" + logVo.getToParam());
            }
        }
    }
}

执行testUserLog()的第一种情况,测试结果:
更改用户信息是否需要审核result:false
更改用户信息生成的日志数量:0

#########################################
执行testUserLog()的第二种情况,测试结果:
更改用户信息是否需要审核result:false
更改用户信息生成的日志数量:1

【身高】从1.23—>1.32

#########################################
执行testUserLog()的第三种情况,测试结果:
更改用户信息是否需要审核result:true
更改用户信息生成的日志数量:4

【姓名】从张三—>李四
【年龄】从23—>64
【生日】从1992-02-23 00:00:00—>1992-06-04 00:00:00
【身高】从1.23—>1.64

#########################################
执行testUserLog()的第四种情况,测试结果:
更改用户信息是否需要审核result:true
更改用户信息生成的日志数量:4

【姓名】从张三—>空
【年龄】从23—>空
【生日】从1992-02-23 00:00:00—>空
【身高】从1.23—>空

#########################################
执行testUserLog()的第五种情况,测试结果:
更改用户信息是否需要审核result:true
更改用户信息生成的日志数量:4

【姓名】从空—>张三
【年龄】从空—>23
【生日】从空—>1992-02-23 00:00:00
【身高】从空—>1.23

》》》》》》》》》》》》》》》》》
#########################################
执行testClassLog()的第一种情况,测试结果:
更改班级信息是否需要审核result:false
更改班级信息生成的日志数量:1
【班级学生总数】从10—>11

#########################################
执行testClassLog()的第二种情况,测试结果:
更改班级信息是否需要审核result:true
更改班级信息生成的日志数量:3
【班级编号】从00001—>00002
【班级学生总数】从10—>20
【班主任】从一班主任—>二班主任

到此个人觉得基本实现了关心的字段从空>非空、非空>空、值1>值2 都记录了对应的变化信息,而且变化的这些字段中如果有重要的需要重新审核也会给需要审核的标记,否则不需要审核标记,可以满足我最开始说的场景

构建的testLogVoList可以直接存数据库或者转成json存到已有日志表的一个字段中,
多个实体都有更改记日志的需求,只需要在对应实体的属性上加上自定义的这个注解,属性的名称可以自己定义

里面可能还有不完善的地方,等我用到项目中再优化看看。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章