JAVA常量编译优化引发的问题

背景介绍

  • 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 反编译,源码正常

具体原因分析

  • 诱发原因是因为:未升级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的版本,这样代码变更带来的影响更加可控。