BUG现场
这是一份若干年前的历史代码,当时的同学写这份代码的时候设计思路是这样的,我复现这份BUG现场的代码如下
要点1 有一个用来传输数据的DTO,大致如下
@Data
public class CategoryBrandBu implements Serializable {
protected static final Map<String,String> BRAND_RELATION_MAP = new HashMap<>();
public Map<String,String> getRelationMap(){
return BRAND_RELATION_MAP;
}
private Long id;
private String brandCode;
private String brandName;
public String getBrandName() {
if (StringUtils.isBlank(brandCode)){
return StringUtils.EMPTY;
}
return BRAND_RELATION_MAP.getOrDefault(brandCode,StringUtils.EMPTY);
}
}
DTO中声明了一个静态常量BRAND_RELATION_MAP
的HashMap,用来保存某个不常更新的表中的映射信息,表中的数据基本只有几十条,所以放在这用来获取品牌名称
要点2. 再另外起一个定时job,定时来刷新这个HashMap中的映射关系数据,代码大意如下
@Configuration
public class ScheduledConfig {
@Scheduled(fixedDelay = 5000)
public void updateMap(){
Random random = new Random();
int i = random.nextInt(1000);
System.out.println("updated: "+i);
CategoryBrandBu dto = new CategoryBrandBu();
dto.getRelationMap().put("ABC", String.valueOf(i));
}
}
其中代码先new CategoryBrandBu()
再getRelationMap()
,再put
的操作虽然看起来比较挫,不过嗯、至少确实还是有用的(原来的代码就是这样),这个不是重点,另外fixedDelay = 5000是我特地调整的5秒刷新一次。
要点3. 接下来一个至关重要的东西,当前Application中提供了对外服务RPC(某自研RPC框架)接口用于查询相关数据,其中有一个接口的返回值中就用到了当前涉及的对象CategoryBrandBu。
RPC基础服务类中将请求参数序列化之后,发送到目标Application中。目标Application接收到请求之后,将请求信息解析之后调用对应bean实例的对应方法,且同时反序列化对应的请求参数为对应方法的java参数对象。这里这序列化和反序列化中用的fastjson(能用,不过也不是很高明的样子)。
BUG表现情况
我们的@Scheduled定时任务无论怎样刷新CategoryBrandBu类中静态成员变量Map的信息,外部应用实例(B)通过RPC访问过来的之后得到的结果永远都是第一次初始化之后得到Map中的值(其实也不是第一次初始化的值,后面我再说到)
举个栗子来说明下,首先当我们的Application(A)启动之后,当前静态成员变量上Map被初始化成了如下
(A)->AAA
(B)->BBB
(C)->CCC
此时外部的应用实例(B)通过RPC访问到当前Application(A)的对应接口之后,得到了这个DTO序列化的结果,其中包含已经被序列化了的BRAND_RELATION_MAP,而我们对应外部应用实例(B)的系统也恰巧用了同样的DTO的java类文件,于是在外部应用实例(B)接收到序列化的返回结果同时,也接收到了序列化的BRAND_RELATION_MAP,外部应用实例(B)对数据进行反序列化,同时也对外部应用实例(B)中的CategoryBrandBu类中的静态成员变量BRAND_RELATION_MAP进行赋值。
Continue reading