背景介绍
- maven项目多模块打包部署,JDK常量编译优化可能引发的血案
- 编译不报错,正常运行
- 代码修改不生效
- debug出现灵异现象
项目结构介绍
-
配置中心两个模块build-api , build-center
- 依赖关系build-center[0.0.1-SNAPSHOT] ------dependency----> build-api[1.0.0-RELEASE]
-
build-api 模板中有一个常量定义类 ConfigConstant ,代码如下
/**
* 用来定义Redis key的前缀
*/
public class ConfigConstant {
public static final String METADATA_CONFIG_PATH = "METADATA_CONFIG_PATH:";
}
- build-center 模块中ConfigRedisConstant类有引用该类,代码如下
/**
* 根据参数组装redis的完整key
*/
public class ConfigRedisConstant {
public static String createMetadataRedisName(String key) {
StringBuilder cacheKey = new StringBuilder(ConfigConstant.METADATA_CONFIG_PATH);
cacheKey.append(key);
return cacheKey.toString();
}
}
改造背景
- redis-cluster集群环境下会出现 keys have defferent slots 异常
- 改造方案,前缀加上花括号 : METADATA_CONFIG_PATH = "{METADATA_CONFIG_PATH:}"
/**
* 用来定义Redis key的前缀
*/
public class ConfigConstant {
public static final String METADATA_CONFIG_PATH = "{METADATA_CONFIG_PATH:}";
}
- 未修改 build-api[1.0.0-RELEASE]模块的版本,打包部署
- 发布后验证,依然出现keys have defferent slots 异常 ,问题没有解决
问题排查
排查1
- 打包问题,代码不是最新的,解压打好的jar包,发现代码是正常的
排查2
- 远程debug
- 查看 ConfigConstant.METADATA_CONFIG_PATH 的值为 {METADATA_CONFIG_PATH:}

- 查看 cacheKey的值为 METADATA_CONFIG_PATH:

- 怀疑类加载问题
- 使用arthas查看类加载器
- sc -d com.test.build.api.ConfigConstant
- sc -d com.test.build.center.ConfigRedisConstant
- 查看属于同一个classLoad : org.springframework.boot.loader.LaunchedURLClassLoader@46d63dbb
- 懵逼中.... 想不明白,为什么debug变量值是对的,但是new StringBuilder() 后值就错了
- 一度怀疑,是不是StringBuilder里面有什么骚操作,自动去除了花括号{}......其实是不可能的。
排查3
-
使用arthas反编译类
-
- jad com.test.build.center.ConfigRedisConstant

- 定位出问题,是因为常量编译优化导致
疑问
- 为何出现debug灵异现象,编译源码和打包的源码不一致?
- 验证
-
不升级 build-api[1.0.0-RELEASE]版本号,本地编译打包
-
com.test.build.api.ConfigConstant 反编译,源码正常

-
com.test.build.center.ConfigRedisConstant 反编译,源码有问题
-

-
-
升级 build-api[1.0.1-RELEASE]版本号,重新打包
- com.test.build.api.ConfigConstant 反编译,源码正常
- com.test.build.center.ConfigRedisConstant 反编译,源码正常
- com.test.build.api.ConfigConstant 反编译,源码正常
-
具体原因分析
-
诱发原因是因为:未升级build-api的版本号
-
由于maven对build-center模块编译的时候,使用了仓库build-api[1.0.0-RELEASE]老的jar包进行编译,而原先的api模块里的代码是没有加过{}的,由于java编译的时候会对静态变量进行编译优化,导致把旧值给编译进去了。
-
最后对项目打包的时候,又使用本地编译好的build-api[1.0.0-RELEASE]模块包进行打包
-
导致debug观察到的现象,很灵异。ConfigConstant里面值是正常的,但是ConfigRedisConstant引用的地方出现错误
建议
- 如果api模块有使用静态变量,并且在非api模块里有引用该静态变量,当静态变量有修改的时候,一定要升级api包的版本,避免灵异现象的出现
- 最佳实践,如果api包里内容有修改,就升级api的版本,这样代码变更带来的影响更加可控。
