背景介紹
短信平台需要支援多種短信的發送,短信文案皆不相同,假設有以下類
@Data
public abstract class MsgBase {
public static final String ALIBABA_GROUP = "阿裡巴巴集團";
public static final String ANT_GROUP = "螞蟻集團";
/**
* 接收人手機号
*/
private String receivePhone;
/**
* 接收人姓名
*/
private String name;
/**
* 咨詢電話
*/
private String consultationPhone;
/**
* 集團名稱
*/
private String groupName;
/**
* 内容模版
* 模版内容中的變量使用${}引用,其中變量名要在成員變量中
*/
private String template;
/**
* 發送
*/
public void send() {
// 替換模版中的變量
replaceTemplate();
// 省略發送 。。。
}
/**
* 替換模版
*/
private void replaceTemplate() {
// 把所有屬性壓到Map中
Map<String, Object> fieldParamMap = Maps.newConcurrentMap();
try {
for (Field field : this.getClass().getDeclaredFields()) {
fieldParamMap.put(field.getName(), BeanUtils.getProperty(this, field.getName()));
}
for (Field field : this.getClass().getSuperclass().getDeclaredFields()) {
fieldParamMap.put(field.getName(), BeanUtils.getProperty(this, field.getName()));
}
// 變量替換 ...
} catch (Exception e) {
throw new RuntimeException("send message Error!", e);
}
}
}
/**
* 充值通知短信
*/
@Data
public class RechargeNoticeMsgBase extends MsgBase {
/**
* 充值日期
*/
private String date;
/**
* 充值金額
*/
private String amount;
}
/**
* 快遞收獲通知短信
*/
@Data
public class ExpressHarvestNoticeMsgBase extends MsgBase {
/**
* 取件碼
*/
private String code;
/**
* 快遞櫃
*/
private String expressDeliveryCabinet;
}
在不同場景下會構造不同的短信,一開始短信中沒有集團名稱,後來才有的需求,後面同學這考慮到集團名稱隻有幾個,是以在Base類中定義了幾個靜态常量,以供子類共享,但是卻帶來了問題:
public class MsgTestMain {
public static void main(String[] args) {
RechargeNoticeMsgBase msg = new RechargeNoticeMsgBase();
msg.setAmount("100");
msg.setDate("2020-12-20");
msg.setGroupName(MsgBase.ALIBABA_GROUP);
msg.setConsultationPhone("13123457890");
msg.setTemplate("【companyName】 ${name} 您好,您在${date}充值了${amount}元,如有疑問請聯系 ${consultationPhone}");
msg.setReceivePhone("13112345678");
msg.setName("張三");
msg.send();
}
}
運作測試類,發現如下異常:
Exception in thread "main" java.lang.RuntimeException: send message Error!
at beanutils.MsgBase.replaceTemplate(MsgBase.java:68)
at beanutils.MsgBase.send(MsgBase.java:49)
at beanutils.MsgTestMain.main(MsgTestMain.java:14)
Caused by: java.lang.NoSuchMethodException: Unknown property 'ALIBABA_GROUP' on class 'class beanutils.RechargeNoticeMsgBase'
at org.apache.commons.beanutils.PropertyUtilsBean.getSimpleProperty(PropertyUtilsBean.java:1270)
at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:809)
at org.apache.commons.beanutils.BeanUtilsBean.getNestedProperty(BeanUtilsBean.java:711)
at org.apache.commons.beanutils.BeanUtilsBean.getProperty(BeanUtilsBean.java:737)
at org.apache.commons.beanutils.BeanUtils.getProperty(BeanUtils.java:380)
at beanutils.MsgBase.replaceTemplate(MsgBase.java:64)
也就是在執行 BeanUtils.getProperty(“ALIBABA_GROUP”)發生異常,目前執行個體沒有這個Field!這是為什麼呢?BeanUtils内部是怎麼實線的呢?
原理說明
跟蹤代碼:
org.apache.commons.beanutils.BeanUtils#getProperty
-> org.apache.commons.beanutils.BeanUtilsBean#getProperty
-> org.apache.commons.beanutils.BeanUtilsBean#getNestedProperty
-> org.apache.commons.beanutils.PropertyUtilsBean#getNestedProperty
-> org.apache.commons.beanutils.PropertyUtilsBean#getSimpleProperty
...
-> org.apache.commons.beanutils.PropertyUtilsBean#getPropertyDescriptors(java.lang.Class)
-> java.beans.Introspector#getBeanInfo(java.lang.Class<?>)
-> java.beans.Introspector#getBeanInfo()
-> java.beans.Introspector#getTargetPropertyInfo
getTargetPropertyInfo方法部分代碼如下圖:

其中getPublicDeclareMethods方法如下:
通過以上代碼可知BeanUtils擷取Field并未通過java.lang.Class#getFields來擷取,而是通過getMethods來擷取,這個方法傳回的是public的方法,并且排除了靜态方法,是以想通過BeanUtils擷取靜态屬性是不行的!
解決方案
- 定義一個常量類,避免使用靜态變量
- 修改replaceTemplae,通過Modifier.isStatic方法判斷是否是靜态變量,如果是将其排除