背景介绍
短信平台需要支持多种短信的发送,短信文案皆不相同,假设有以下类
@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方法判断是否是静态变量,如果是将其排除