反射比较两个对象属性值是否有修改

本文介绍了一个基于SpringMVC的应用案例,该应用通过控制请求映射实现了采购报价SKU的保存功能。当新建或更新采购报价SKU时,系统会记录操作日志,并通过反射比较新旧对象差异来记录具体的修改内容。
springMVC Control



@RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(RedirectAttributes redirectAttr,
GoodsSupplierPrice gsp,
@ModelAttribute(App.SESSION_USER_KEY) SessionUser sessionUser) {
if (gsp.getId() == null) {
gsp.setOperUserId(sessionUser.getUserId());
this.goodsSupplierPriceService.save(gsp);
operateLogService.insert("insert", "插入-采购报价SKU:"+gsp.getGoodsSku(), sessionUser.getUserId(), "采购管理", "采购报价管理");
} else {
GoodsSupplierPrice gspOld = this.goodsSupplierPriceService.load(gsp.getId());
try{
String editMsg= CommonUtil.getEditMsg(gspOld,gsp,gsp.getClass().getName());
this.goodsSupplierPriceService.update(gsp);
operateLogService.insert("update", "更新-采购报价SKU:"+gsp.getGoodsSku()+"["+editMsg+"]", sessionUser.getUserId(), "采购管理", "采购报价管理");
}catch(Exception e){
e.printStackTrace();
}
}



实体对象


import java.math.BigDecimal;
import java.util.Date;

import org.springframework.format.annotation.DateTimeFormat;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.xuanfeiyang.annotations.Column;

public class GoodsSupplierPrice {
private Integer id;

private Integer supplierId;

@Column(Desc = "SKU")
private String goodsSku;

@Column(Desc = "商品名称")
private String goodsName;

@Column(Desc = "商品单位")
private String goodsUnit;

@Column(Desc = "采购量从")
private Integer countMin;

@Column(Desc = "采购量到")
private Integer countMax;

@Column(Desc = "报价")
private BigDecimal price;

@JsonFormat(pattern="yyyy-MM-dd", timezone="GMT+8")
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date startDate;

@JsonFormat(pattern="yyyy-MM-dd", timezone="GMT+8")
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date endDate;

@Column(Desc = "采购周期")
private Short buyPeriod;

@Column(Desc = "优先级")
private Short priority;

@Column(Desc = "备注")
private String note;

private Integer operUserId;

private Date createdTime;

private Date lastUpdatedTime;

// 附件字段:供应商名称
private String supplierName;

// 附件字段:维护人名称
private String operUserName;

public String getSupplierName() {
return supplierName;
}

public void setSupplierName(String supplierName) {
this.supplierName = supplierName;
}

public String getOperUserName() {
return operUserName;
}

public void setOperUserName(String operUserName) {
this.operUserName = operUserName;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public Integer getSupplierId() {
return supplierId;
}

public void setSupplierId(Integer supplierId) {
this.supplierId = supplierId;
}

public String getGoodsSku() {
return goodsSku;
}

public void setGoodsSku(String goodsSku) {
this.goodsSku = goodsSku == null ? null : goodsSku.trim();
}

public String getGoodsName() {
return goodsName;
}

public void setGoodsName(String goodsName) {
this.goodsName = goodsName == null ? null : goodsName.trim();
}

public String getGoodsUnit() {
return goodsUnit;
}

public void setGoodsUnit(String goodsUnit) {
this.goodsUnit = goodsUnit == null ? null : goodsUnit.trim();
}

public Integer getCountMin() {
return countMin;
}

public void setCountMin(Integer countMin) {
this.countMin = countMin;
}

public Integer getCountMax() {
return countMax;
}

public void setCountMax(Integer countMax) {
this.countMax = countMax;
}

public BigDecimal getPrice() {
return price;
}

public void setPrice(BigDecimal price) {
this.price = price;
}

public Date getStartDate() {
return startDate;
}

public void setStartDate(Date startDate) {
this.startDate = startDate;
}

public Date getEndDate() {
return endDate;
}

public void setEndDate(Date endDate) {
this.endDate = endDate;
}

public Short getBuyPeriod() {
return buyPeriod;
}

public void setBuyPeriod(Short buyPeriod) {
this.buyPeriod = buyPeriod;
}

public Short getPriority() {
return priority;
}

public void setPriority(Short priority) {
this.priority = priority;
}

public String getNote() {
return note;
}

public void setNote(String note) {
this.note = note == null ? null : note.trim();
}

public Integer getOperUserId() {
return operUserId;
}

public void setOperUserId(Integer operUserId) {
this.operUserId = operUserId;
}

public Date getCreatedTime() {
return createdTime;
}

public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}

public Date getLastUpdatedTime() {
return lastUpdatedTime;
}

public void setLastUpdatedTime(Date lastUpdatedTime) {
this.lastUpdatedTime = lastUpdatedTime;
}
}




工具类



/**
* 数据修改对比统计
* @param oldT 修改前
* @param newT 修改后
* @param className 类名
* @param <T>
* @return
* @throws Exception
*/
public static <T> String getEditMsg(T oldT, T newT, String className) throws Exception{
Class onwClass = Class.forName(className);
StringBuffer editMsg = new StringBuffer();
Field[] fields = onwClass.getDeclaredFields();
for (Field f : fields) {
// 过滤 static、 final、private static final字段
if (f.getModifiers() == 16 || f.getModifiers() == 8
|| f.getModifiers() == 26) {
continue;
}
com.xuanfeiyang.annotations.Column annotationColumn = f.getAnnotation(com.xuanfeiyang.annotations.Column.class);
if (annotationColumn == null) {
continue;
}
Object oldV = ReflectUtil.getValueMethod(oldT,f.getName(),f.getType());
Object newV = ReflectUtil.getValueMethod(newT,f.getName(),f.getType());
if (newV != null && !newV.equals(oldV) && StringUtils.isNotBlank(newV.toString())) {
editMsg.append(annotationColumn.Desc() + " 从 " + oldV + "修改成" + newV + "<br>");
}
}
return editMsg.toString();
}

<think> 我们正在讨论Java反射机制。用户询问如何通过反射为已有a和b属性对象动态新增一个c属性。注意,在Java中,类的结构在编译后是固定的,我们不能直接通过反射为已有类添加新字段。但是,我们可以通过一些技巧来实现类似的效果。 参考引用: [^1]提到反射机制可以动态获取类的属性和方法,并且可以动态调用方法,还提到了Constructor、Field、Method等类。 [^2]提到所有Java类都继承自Object,其中getClass()方法返回Class对象。 [^4]展示了如何使用反射操作对象属性,包括通过注解标记需要比对的字段。 [^5]展示了一个动态设置属性值的方法,通过字符串拼接属性名来设置值。 但是,直接为对象新增属性(即添加一个新的字段)在标准的Java反射中是不允许的,因为字段是类定义的一部分。不过,我们可以考虑以下替代方案: 1. 使用Map来存储额外的属性(即动态字段)。 2. 使用动态代理。 3. 使用字节码操作库(如ASM、CGLIB)在运行时动态创建新类,但这已经超出了核心反射API的范围。 然而,用户明确要求通过反射实现,并且希望为已有对象新增属性。由于反射本身不能修改已有类的结构,我们只能借助其他方式。 一种常见的做法是将对象包装在一个可以持有额外属性的类中(比如使用装饰器模式),或者使用Map来存储额外的属性。但这里用户似乎希望直接在原对象上增加属性,这实际上是不可能的。 但是,如果我们重新考虑:用户的对象已经有了a和b,现在要新增c,我们可以通过创建一个新的类,该类包含a、b和c,然后复制原对象的a和b的值,并设置c的值,从而达到“新增”的效果。但这并不是在原有对象上直接添加。 另一种思路:如果用户只是希望在运行时动态地给对象附加一些信息,而不改变原有类的结构,可以使用Map<String, Object>来存储这些额外的属性,然后将这个Map作为对象的一个字段(比如叫attributes),然后通过反射操作这个Map。但前提是这个对象已经预留了这样一个Map字段。 然而,用户并没有说明对象是否有这样的预留字段。因此,我们需要分情况: 情况1:如果对象已经有一个Map类型的字段用于存放动态属性,那么我们可以通过反射获取这个Map字段,然后往Map中添加一个键值对(如"c", value)。 情况2:如果对象没有这样的字段,那么我们可以考虑使用一个外部的Map来存储每个对象的额外属性。例如,使用WeakHashMap<Object, Map<String, Object>>,这样我们就可以通过对象实例作为键,其对应的动态属性作为值。 但是,用户要求通过反射来实现,而且要求给对象新增属性。这里我理解用户可能希望在不修改类定义的情况下,让对象拥有新的属性。 鉴于上述限制,我建议: 1. 如果用户能够控制类的定义,可以在类中添加一个Map字段用于存放动态属性,然后通过反射来操作这个Map。 2. 如果用户不能修改类,那么可以使用外部存储(如WeakHashMap)来关联对象和它的动态属性。 下面,我将分别给出两种方案的示例: 方案1:类中预留Map字段(假设类名为MyObject) ```java public class MyObject { private String a; private String b; private Map<String, Object> dynamicProperties = new HashMap<>(); // 用于存放动态属性 // 省略getter和setter public Object getDynamicProperty(String key) { return dynamicProperties.get(key); } public void setDynamicProperty(String key, Object value) { dynamicProperties.put(key, value); } } ``` 然后通过反射操作这个Map: ```java // 假设我们有一个MyObject的实例obj MyObject obj = new MyObject(); // 我们想通过反射给obj新增一个属性c,值为123 Field dynamicPropertiesField = obj.getClass().getDeclaredField("dynamicProperties"); dynamicPropertiesField.setAccessible(true); Map<String, Object> map = (Map<String, Object>) dynamicPropertiesField.get(obj); map.put("c", 123); ``` 方案2:使用外部WeakHashMap(适用于没有预留Map字段的情况) 我们可以创建一个工具类: ```java import java.util.Map; import java.util.WeakHashMap; import java.util.HashMap; public class DynamicPropertyUtil { private static final Map<Object, Map<String, Object>> DYNAMIC_PROPERTIES = new WeakHashMap<>(); public static void setProperty(Object obj, String propertyName, Object value) { Map<String, Object> props = DYNAMIC_PROPERTIES.computeIfAbsent(obj, k -> new HashMap<>()); props.put(propertyName, value); } public static Object getProperty(Object obj, String propertyName) { Map<String, Object> props = DYNAMIC_PROPERTIES.get(obj); return props == null ? null : props.get(propertyName); } } ``` 然后,我们可以这样使用: ```java // 假设有一个已有对象obj(可以是任意类型,不需要有Map字段) SomeClass obj = new SomeClass(); // 这个类只有a和b属性 // 动态添加属性c DynamicPropertyUtil.setProperty(obj, "c", "this is c's value"); // 获取属性c Object cValue = DynamicPropertyUtil.getProperty(obj, "c"); ``` 注意:方案2中,WeakHashMap的键是弱引用,不会阻止对象被垃圾回收。但是,如果我们需要为大量对象添加动态属性,要注意内存管理。 总结:由于Java反射不允许直接为已有对象添加新的字段(因为字段是类级别的),我们只能通过间接方式实现类似功能。 如果用户坚持要直接给对象添加一个新字段(即改变对象的类结构),那么就必须使用字节码操作库(如ASM、Javassist)在运行时创建一个新的类(包含原字段和新字段),然后生成一个新类,并将原对象的数据复制过去。但这种方式较为复杂,且性能开销较大。 因此,根据用户的需求,我推荐上述两种方案之一。 由于用户的问题是如何通过反射对象新增属性,而方案1中我们通过反射操作了对象中的Map字段,方案2则没有使用反射(而是使用外部工具类)。如果用户要求必须使用反射操作对象本身,那么方案1是更合适的。 所以,我假设用户能够修改原有的类,添加一个Map字段。然后通过反射操作这个Map字段来达到添加动态属性的目的。 如果用户不能修改原有类,那么方案2是可行的,但方案2并没有使用反射操作对象本身,而是使用了外部存储。 因此,我给出的主要示例是方案1(使用反射操作预留的Map字段)。如果不能修改类,则采用方案2。 下面我将用代码展示方案1的详细步骤(假设类中已有Map字段): </think> 在 Java 中,由于类的结构在编译后是固定的,**无法直接通过反射为已有对象添加新属性**(如为已有类添加 `c` 属性字段)。但可以通过以下两种间接方案实现类似功能: --- ### 方案一:使用动态属性容器(推荐) 通过在对象内部预置 `Map` 字段存储扩展属性: ```java import java.util.HashMap; import java.util.Map; public class DynamicObject { // 固定属性 private String a; private String b; // 动态属性容器 private Map<String, Object> dynamicProperties = new HashMap<>(); // 反射操作动态属性 public void addProperty(String key, Object value) { dynamicProperties.put(key, value); } public Object getProperty(String key) { return dynamicProperties.get(key); } // 原有属性的 getter/setter ... } ``` **通过反射操作动态属性**: ```java // 创建对象 DynamicObject obj = new DynamicObject(); // 反射获取并操作 dynamicProperties 字段 Field field = obj.getClass().getDeclaredField("dynamicProperties"); field.setAccessible(true); // 突破私有访问限制 Map<String, Object> properties = (Map<String, Object>) field.get(obj); properties.put("c", "新增的属性值"); // 动态添加 c 属性 System.out.println(properties.get("c")); // 输出: 新增的属性值 ``` --- ### 方案二:使用外部属性映射(无侵入式) 通过外部 `WeakHashMap` 关联对象与扩展属性: ```java import java.util.Map; import java.util.WeakHashMap; public class PropertyExtension { // 外部属性存储 (自动回收无引用对象) private static final Map<Object, Map<String, Object>> EXTENDED_PROPERTIES = new WeakHashMap<>(); // 添加扩展属性 public static void addProperty(Object obj, String key, Object value) { Map<String, Object> props = EXTENDED_PROPERTIES.computeIfAbsent( obj, k -> new HashMap<>() ); props.put(key, value); } // 获取扩展属性 public static Object getProperty(Object obj, String key) { return EXTENDED_PROPERTIES.getOrDefault(obj, Map.of()).get(key); } } ``` **使用示例**: ```java // 原始对象 (只有 a, b 属性) class Original { /* a,b 字段 */ } Original obj = new Original(); // 动态添加 c 属性 PropertyExtension.addProperty(obj, "c", "扩展值"); // 获取 c 属性 System.out.println(PropertyExtension.getProperty(obj, "c")); // 输出: 扩展值 ``` --- ### 关键说明 1. **反射限制** Java 反射 API(如 `Field`)仅能修改**已有字段的值**,无法添加新字段到已有类中[^1][^3]。 2. **备选方案** - 若需真正的字段扩展,需使用字节码操作库(如 ASM、Javassist) - 使用动态代理(`java.lang.reflect.Proxy`)生成包含新属性的子类 3. **应用场景** 框架中动态扩展元数据(如 Spring 的 `@Transient` 注解)常采用方案一,而方案二适用于日志记录等无侵入扩展[^4]。 > **建议**:优先采用方案一,通过预留扩展容器实现安全可控的动态属性管理。 --- ### 相关问题 1. Java 反射修改 `final` 字段会有什么风险? 2. 如何通过反射调用对象的私有方法? 3. 字节码操作库(如 ASM)与反射在功能上有何本质区别? 4. `WeakHashMap` 在方案二中是如何避免内存泄漏的? [^1]: 反射机制动态获取类信息 [^3]: 反射触发类初始化条件 [^4]: 反射在日志记录中的应用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值