自定义注解利用反射记录日志
场景:
项目中有一个供应商模块,包含供应商的基础信息、供应商资质等,字段比较多,其中有些信息比较重要,如果修改了需要记录修改日志(如哪个信息从什么改为什么),同时还需要重新提交,而有些信息修改了值需要记下日志,并不需要重新审核,还有一些无关紧要的信息修改了无需记录日志,也不需要审核
最开始项目中记录日志只针对三四个关键字段,比较简单,直接用待更新的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 lyc 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 lyc 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 lyc 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 lyc 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 lyc 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 lyc 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){// 如果类型是Stringif (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);}}// 如果类型是Integerif (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());}}// 如果类型是Doubleif (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);
// }
// }// 如果类型是Dateif (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 lyc 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存到已有日志表的一个字段中,
多个实体都有更改记日志的需求,只需要在对应实体的属性上加上自定义的这个注解,属性的名称可以自己定义
里面可能还有不完善的地方,等我用到项目中再优化看看。
自定义注解利用反射记录日志相关推荐
- java 获取类的注解_Java 自定义注解通过反射获取类、方法、属性上的注解
反射 JAVA中的反射是运行中的程序检查自己和软件运行环境的能力,它可以根据它发现的进行改变.通俗的讲就是反射可以在运行时根据指定的类名获得类的信息. 注解的定义 注解通过 @interface 关键 ...
- 【Java】灵活使用自定义注解和反射
完整代码 import java.lang.annotation.*; import java.lang.reflect.Method;@Target(ElementType.METHOD) @Ret ...
- ssm+redis 如何更简洁的利用自定义注解+AOP实现redis缓存
基于 ssm + maven + redis 使用自定义注解 利用aop基于AspectJ方式 实现redis缓存 如何能更简洁的利用aop实现redis缓存,话不多说,上demo 需求: 数据查询时 ...
- 自定义注解导出excel数据
自定义注解导出excel数据 利用自定义注解方式,对数据列表进行简单的导出操作.即在实体对象的属性域上添加导出标识的注解,在对实体进行导出时,利用自定义注解进行反射的方法,获取实体需要导出的属性及值. ...
- java注解判断字段是否存在_使用注解和反射判断指定的字段不能为空
我们在写项目的时候,如何类比较少.判别指定对象的属性值是否为空,那确实可以,但是随着类的增多,判别对象的属性是否为空就非常的繁琐,所以可以使用自定义注解和反射来判定指定的字段是否为空. 第一步:创建一 ...
- (Java)注解和反射
文章目录 注解和反射 一. 注解 1.1 元注解 1.2 内置注解 1.3 自定义注解 二. 反射 2.1 什么是反射 2.2 Class类 2.3 创建Class类的方式 2.4 所有类型的Clas ...
- java 自定义注解+AOP实现日志记录
ssm版本: 1.首先自定义一个注解,该注解有两个属性,一个是模块名,一个是操作的内容.该注解是用来修饰Service层中的方法的. 2.创建一个切面类,该切面使用@Aspect和@Component ...
- java运行时读取注解_Java自定义注解和运行时靠反射获取注解
转:1.Annotation的工作原理: JDK5.0中提供了注解的功能,允许开发者定义和使用自己的注解类型.该功能由一个定义注解类型的语法和描述一个注解声明的语法,读取注解的API,一个使用注解修饰 ...
- 利用自定义注解实现权限验证
思路: 根据自定义注解,给对应权限能够查看到的资源的Controller方法上添加注解(也就是一个权限字符串),权限字符串跟方法是一对多的关系,同一个权限字符串可以添加到多个方法上:当用户有对应的权限 ...
最新文章
- DeepMind让AI变身天才数学家!首次提出两大数学猜想,登Nature封面
- MQTT:物联网的神经系统
- 通过Ajax来简单的实现局部刷新(主要为C#中使用的UpdatePanel控件和ScriptManager控件)...
- 吞吐量达到瓶颈后下降_如何找到 Kafka 集群的吞吐量极限?
- 是AI就躲个飞机-纯Python实现人工智能
- javaWeb_JSP 动态指令 forward 的程序
- Python 中的 os 模块常见方法
- 10-多写一个@Autowired导致程序崩了
- idea maven创建java项目_新版本IntelliJ IDEA 构建maven,并用Maven创建一个web项目(图文教程)...
- freemarker的测试结果框架_java必背综合知识点总结(框架篇)
- 三种新型DDoS反射攻击出现
- 值得收藏的130个神器网站
- thinkphp5 php代码中如何确定文件的路径位置
- 数学杂谈:高维空间向量夹角小记
- 数据库课程设计-KTV点歌系统(VB.net 窗体,MySQL5.5)
- jdk8Comparator.comparing 排序
- 用python画笛卡尔心形线_05.总是套路得人心,如何用数学+Python示爱!
- Docker入门实战大全终极版
- 戴尔r810服务器 安装系统,2U4路超高密度 戴尔PE R810服务器拆解(二)
- 更改计算机名蓝屏,新机型win10改win7系统开机出现蓝屏代码0X000000a5解决方法