Java使用動(dòng)態(tài)代理和反射實(shí)現(xiàn)字段變更跟蹤的方案
下面我將展示如何使用Java動(dòng)態(tài)代理和反射技術(shù)來跟蹤記錄對(duì)象字段的變更前和變更后的數(shù)據(jù)。
實(shí)現(xiàn)方案
我們將創(chuàng)建一個(gè)FieldChangeTracker代理,它能夠:
- 在字段被修改前記錄原始值
- 在字段被修改后記錄新值
- 將所有變更記錄保存在日志中
1. 創(chuàng)建變更記錄數(shù)據(jù)結(jié)構(gòu)
import java.util.Date;
public class FieldChangeRecord {
private String fieldName;
private Object oldValue;
private Object newValue;
private Date changeTime;
private String methodName;
public FieldChangeRecord(String fieldName, Object oldValue, Object newValue, String methodName) {
this.fieldName = fieldName;
this.oldValue = oldValue;
this.newValue = newValue;
this.changeTime = new Date();
this.methodName = methodName;
}
@Override
public String toString() {
return "FieldChangeRecord{" +
"fieldName='" + fieldName + '\'' +
", oldValue=" + oldValue +
", newValue=" + newValue +
", changeTime=" + changeTime +
", methodName='" + methodName + '\'' +
'}';
}
}2. 創(chuàng)建變更跟蹤處理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class FieldChangeTracker implements InvocationHandler {
private final Object target;
private final List<FieldChangeRecord> changeHistory = new ArrayList<>();
public FieldChangeTracker(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 只攔截setter方法
if (method.getName().startsWith("set") && args != null && args.length == 1) {
// 獲取字段名(從setXxx轉(zhuǎn)換為xxx)
String fieldName = method.getName().substring(3);
fieldName = Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1);
try {
// 獲取字段當(dāng)前值(變更前)
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
Object oldValue = field.get(target);
// 調(diào)用原始方法修改值
Object result = method.invoke(target, args);
// 獲取字段新值(變更后)
Object newValue = field.get(target);
// 記錄變更
changeHistory.add(new FieldChangeRecord(
fieldName, oldValue, newValue, method.getName()));
return result;
} catch (NoSuchFieldException e) {
// 如果沒有對(duì)應(yīng)字段,直接調(diào)用方法
return method.invoke(target, args);
}
} else {
// 非setter方法直接調(diào)用
return method.invoke(target, args);
}
}
public List<FieldChangeRecord> getChangeHistory() {
return new ArrayList<>(changeHistory);
}
public void printChangeHistory() {
System.out.println("=== Field Change History ===");
for (FieldChangeRecord record : changeHistory) {
System.out.println(record);
}
System.out.println("===========================");
}
}3. 創(chuàng)建代理工廠
import java.lang.reflect.Proxy;
public class TrackingProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T createTrackingProxy(T target, Class<T> interfaceType) {
return (T) Proxy.newProxyInstance(
interfaceType.getClassLoader(),
new Class<?>[]{interfaceType},
new FieldChangeTracker(target));
}
}4. 示例使用
public interface User {
String getName();
void setName(String name);
int getAge();
void setAge(int age);
}
public class UserImpl implements User {
private String name;
private int age;
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public int getAge() {
return age;
}
@Override
public void setAge(int age) {
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
User realUser = new UserImpl();
User userProxy = TrackingProxyFactory.createTrackingProxy(realUser, User.class);
userProxy.setName("Alice");
userProxy.setAge(25);
userProxy.setName("Bob");
userProxy.setAge(30);
// 獲取變更歷史
FieldChangeTracker tracker = (FieldChangeTracker) Proxy.getInvocationHandler(userProxy);
tracker.printChangeHistory();
}
}5. 示例輸出
運(yùn)行上面的Main類后,輸出可能如下:
=== Field Change History ===
FieldChangeRecord{fieldName='name', oldValue=null, newValue=Alice, changeTime=..., methodName='setName'}
FieldChangeRecord{fieldName='age', oldValue=0, newValue=25, changeTime=..., methodName='setAge'}
FieldChangeRecord{fieldName='name', oldValue=Alice, newValue=Bob, changeTime=..., methodName='setName'}
FieldChangeRecord{fieldName='age', oldValue=25, newValue=30, changeTime=..., methodName='setAge'}
===========================高級(jí)改進(jìn)
- 支持非接口類:可以使用CGLIB庫來代理沒有接口的類
- 線程安全:為changeHistory添加同步控制
- 過濾敏感字段:添加注解標(biāo)記不需要跟蹤的字段
- 持久化存儲(chǔ):將變更記錄保存到數(shù)據(jù)庫或文件
- 性能優(yōu)化:緩存反射獲取的Field對(duì)象
CGLIB版本實(shí)現(xiàn)
如果需要代理沒有實(shí)現(xiàn)接口的類,可以使用CGLIB:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibTrackingProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T createTrackingProxy(T target) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new CglibFieldChangeTracker(target));
return (T) enhancer.create();
}
}
class CglibFieldChangeTracker implements MethodInterceptor {
private final Object target;
private final List<FieldChangeRecord> changeHistory = new ArrayList<>();
public CglibFieldChangeTracker(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 實(shí)現(xiàn)邏輯與FieldChangeTracker類似
// ...
}
// 其他方法與FieldChangeTracker相同
}這種實(shí)現(xiàn)方式可以跟蹤任何類的字段變更,而不僅限于實(shí)現(xiàn)了接口的類。
到此這篇關(guān)于Java使用動(dòng)態(tài)代理和反射實(shí)現(xiàn)字段變更跟蹤的方案的文章就介紹到這了,更多相關(guān)Java動(dòng)態(tài)代理和反射跟蹤字段變更內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一步步教你把SpringBoot項(xiàng)目打包成Docker鏡像
Docker可以讓開發(fā)者打包他們的應(yīng)用以及依賴包到一個(gè)輕量級(jí)、可移植的容器中,然后發(fā)布到任何流行的 Linux 機(jī)器上,也可以實(shí)現(xiàn)虛擬化,下面這篇文章主要給大家介紹了關(guān)于SpringBoot項(xiàng)目打包成Docker鏡像的相關(guān)資料,需要的朋友可以參考下2023-02-02
java實(shí)現(xiàn)字符串和數(shù)字轉(zhuǎn)換工具
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)字符串和數(shù)字轉(zhuǎn)換工具,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04
Java多線程(單例模式,阻塞隊(duì)列,定時(shí)器,線程池)詳解
本文是多線程初級(jí)入門,主要介紹了多線程單例模式、阻塞隊(duì)列、定時(shí)器、線程池、多線程面試考點(diǎn),感興趣的小伙伴可以跟隨小編一起了解一下2022-09-09
使用SpringBoot+EasyExcel+Vue實(shí)現(xiàn)excel表格的導(dǎo)入和導(dǎo)出詳解
這篇文章主要介紹了使用SpringBoot+VUE+EasyExcel?整合導(dǎo)入導(dǎo)出數(shù)據(jù)的過程詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08
spring cloud學(xué)習(xí)入門之config配置教程
這篇文章主要給大家介紹了關(guān)于spring cloud學(xué)習(xí)入門之config配置的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring cloud具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09
Java郵件發(fā)送超時(shí)時(shí)間過長問題的優(yōu)化方案
郵件發(fā)送是許多Java應(yīng)用中常見的功能,尤其在用戶注冊(cè)、密碼重置和系統(tǒng)通知中,對(duì)于一個(gè)高并發(fā)的系統(tǒng)來說,郵件發(fā)送的超時(shí)問題可能導(dǎo)致應(yīng)用性能的下降,甚至影響用戶體驗(yàn),因此,本期我們將討論Java郵件發(fā)送超時(shí)時(shí)間過長 的問題,并深入探討其成因和優(yōu)化策略2025-06-06
springboot的EnvironmentPostProcessor接口方法源碼解析
這篇文章主要介紹了springboot的EnvironmentPostProcessor接口方法源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
SpringBoot2.x中management.security.enabled=false無效的解決
這篇文章主要介紹了SpringBoot2.x中management.security.enabled=false無效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
java 枚舉類定義靜態(tài)valueOf(java.lang.String)方法的問題及解決
這篇文章主要介紹了java 枚舉類定義靜態(tài)valueOf(java.lang.String)方法的問題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09

