場景:
項目中有一個供應商模塊,包含供應商的基礎信息、供應商資質等,字段比較多,其中有些信息比較重要,如果修改了需要記錄修改日誌(如哪個信息從什麼改爲什麼),同時還需要重新提交,而有些信息修改了值需要記下日誌,並不需要重新審覈,還有一些無關緊要的信息修改了無需記錄日誌,也不需要審覈
最開始項目中記錄日誌只針對三四個關鍵字段,比較簡單,直接用待更新的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存到已有日誌表的一個字段中,
多個實體都有更改記日誌的需求,只需要在對應實體的屬性上加上自定義的這個註解,屬性的名稱可以自己定義
裏面可能還有不完善的地方,等我用到項目中再優化看看。