问题发现
周二晚上开始上线
- 测试环境验证通过
- 预发环境验证通过
- 开始生产上线
生产上线完成,通知测试进行验证。
测试开始走测试用例,发现一直失败。
紧急通知正在与同事打屁的我,怎么可能有问题呢?预发不是验证通过了么?绝对不会有问题,你再试试。然并卵,真的有问题。
oh my god。。。。哪里的问题呢?
问题排查
经过一顿排查(看日志记录),还真是发现了问题:
测试环境与预发环境的消息都是自己发送自己消费,不已经过消息序列化与反序列化的步骤,所以验证通过。生产环境要保证高可用,因此会将消息异步化,通过jmq来保证高可用,利用JSON序列化与反序列化。在这里发现了问题日志,主属性丢失了
问题代码:
基础事件定义:
/**
* @Title: EventMsg
* @Description: 事件消息
*/
public class EventMsg {
private String id;
private String eventType;
private String content;
public EventMsg(){}
public EventMsg(String eventType, String content) {
this.id = content;
this.eventType = eventType;
this.content = content;
}
public EventMsg(String eventType, String content, String id) {
this.id = id;
this.eventType = eventType;
this.content = content;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getEventType() {
return eventType;
}
public void setEventType(String eventType) {
this.eventType = eventType;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "EventMsg{" +
"eventType=" + eventType +
", content='" + content + '\'' +
'}';
}
}
继承基础事件定义的产品事件:
主要是拓展产品域事件的额外信息
/**
* @date 2021/1/15
**/
public class ItemEvent extends EventMsg {
/**
* 商品域内部事件类别
* #{ItemEventTypeEum}
*/
private String itemEventType;
/**
* 分发记录id
*/
private Long distributId;
public String getItemEventType() {
return itemEventType;
}
public void setItemEventType(String itemEventType) {
this.itemEventType = itemEventType;
}
public Long getDistributId() {
return distributId;
}
public ItemEvent setDistributId(Long distributId) {
this.distributId = distributId;
return this;
}
public ItemEvent() {
}
public ItemEvent(String itemEventType, String content) {
super(EventTypeEum.item.name(), content);
this.itemEventType = itemEventType;
}
public ItemEvent(String itemEventType, String skuId, Long distributId) {
super(EventTypeEum.item.name(), skuId);
this.itemEventType = itemEventType;
this.distributId = distributId;
}
}
这里要注意新增了一个构造函数
public ItemEvent(String itemEventType, String skuId, Long distributId) {
super(EventTypeEum.item.name(), skuId);
this.itemEventType = itemEventType;
this.distributId = distributId;
}
通过调用父级的完成基础事件初始化。此处注意content与产品消息的skuId。
public EventMsg(String eventType, String content, String id) {
this.id = id;
this.eventType = eventType;
this.content = content;
}
单元测试:
`@Test`
`public` `void` `test_buildEventMsg() {`
` ``String message = ``"{"content":"18468377001001000021","eventType":"item","id":"18468377001001000021","itemEventType":"UPDATED"}"``;`
` ``EventMsg eventMsg = EventListener.buildEventMsg(message);`
` ``Assert.assertEquals(``true``, eventMsg ``instanceof` `ItemEvent);`
` ``Assert.assertEquals(``"18468377001001000021"``, eventMsg.getContent());`
`}`
`@Test`
`public` `void` `test_buildEventMsg_withDistributeId() {`
` ``String message = ``"{"distributId":111,"content":"18468377001001000021","eventType":"item","id":"18468377001001000021","itemEventType":"UPDATED"}"``;`
` ``EventMsg eventMsg = EventListener.buildEventMsg(message);`
` ``Assert.assertEquals(``true``, eventMsg ``instanceof` `ItemEvent);`
` ``Assert.assertEquals(``new` `Long(``111``), ((ItemEvent) eventMsg).getDistributId());`
`}`
结果发现content丢了。
为啥丢了呢?
1、当存在多个构造函数的情况(不存在空构造函数),会选择参数最多的一个。
2、同时会根据json匹配参数名称映射字段名称取值,如果参数名称与属性(包含父级)名称不一致,会发生属性获取失败的情况
根据如上第一条说明,发现实际调用了第二个构造函数
public ItemEvent(String itemEventType, String content)
public ItemEvent(String itemEventType, String skuId, Long distributId)
再根据第二条检查,发现实际属性应该是content, 传入的是skuId。parseObject会通过参数名称自动反射属性,这时发现没有setSkuId的属性赋值方法,丢弃。这里已经看明白为啥content没值了吧。
解决方案呢:
- 符合Java Bean规范,存在空构造函数
- 调整构造函数参数与属性保持一致
这个坑还真没注意过。大家可以看下parseObject的代码跟一下。