天天看點

BeanUtils getProperty的一個坑

背景介紹

短信平台需要支援多種短信的發送,短信文案皆不相同,假設有以下類

@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方法部分代碼如下圖:

BeanUtils getProperty的一個坑

其中getPublicDeclareMethods方法如下:

BeanUtils getProperty的一個坑

通過以上代碼可知BeanUtils擷取Field并未通過java.lang.Class#getFields來擷取,而是通過getMethods來擷取,這個方法傳回的是public的方法,并且排除了靜态方法,是以想通過BeanUtils擷取靜态屬性是不行的!

解決方案

  • 定義一個常量類,避免使用靜态變量
  • 修改replaceTemplae,通過Modifier.isStatic方法判斷是否是靜态變量,如果是将其排除