老板让我把一个项目从 Java 8 迁移到 Java 11,我该怎么办呢?
➜ Downloads tree emt4j-0.3
emt4j-0.3
├── bin
│ ├── analysis.bat
│ └── analysis.sh
└── lib
├── agent
│ ├── emt4j-agent-jdk11-0.3.jar
│ └── emt4j-agent-jdk8-0.3.jar
├── analysis
│ ├── asm-9.2.jar
│ ├── commons-io-2.4.jar
│ ├── commons-lang3-3.8.jar
│ ├── emt4j-analysis-0.3.jar
│ ├── emt4j-common-0.3.jar
│ ├── gson-2.9.0.jar
│ ├── lombok-1.18.8.jar
│ ├── mvel2-2.4.12.Final.jar
│ ├── slf4j-api-1.7.30.jar
│ └── velocity-engine-core-2.3.jar
└── maven-plugin
└── emt4j-maven-plugin-0.3.jar
emt4j-agent-jdk8-0.3.jar
emt4j-agent-jdk11-0.3.jar
public class Hello {
public static void main( String[] args ) {
System.out.println( "Hello World!" );
}
}
java -javaagent:emt4j-agent-jdk8-0.3.jar=to=11 Hello
sh analysis.sh -o report.html emt4j-XXX.dat
// log4j-core:2.10.0
private static boolean isPreJava8() {
String version = System.getProperty("java.version");
String[] parts = version.split("\\.");
try {
int major = Integer.parseInt(parts[1]);
return major < 8;
} catch (Exception var3) {
return true;
}
}
如果使用 Java 11 来运行这个方法,会得到 true,也就是认为 JDK11 是 JDK8 之前的版本,这显然是不对的,简单 debug 一下就知道错在哪了。 当然,我使用的版本是 log4j-core:2.10.0,我相信大名鼎鼎的 log4j 项目一定在之后的版本修复了这个问题。 果然,在某一次 commit 上就专门修复了这个问题。 修复的方式也很 low,就是判断第一个点前面如果是 1,就按照新的方式做判断,即把点后面的数字作为主版本。 嗯,那看来,把这个项目从 8 升级到 11,最稳妥的方式是连这个使用老版本的 log4j 三方依赖也同时升级了。class Hello {
public static void main(String[] var0) {
System.out.println("hello");
System.out.println(System.getProperty("java.version"));
}
}
sh analysis.sh -f 8 -t 11 -o report.html classFiles
这个 sh 脚本其实就是执行 AnalysisMain 的主方法,并且将上面几个参数作为 args 传入进去。
// org/eclipse/emt4j/analysis/AnalysisMain.java
public class AnalysisMain {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, URISyntaxException {
...
ReportConfig config = doAnalysis(args, ...);
doReport(config, ...);
}
...
}
所以从这个入口开始看起就好了。 主方法的结构也很简单,doAnalysis 就是对这些 class 文件进行分析,得出结果。doReport 就是将分析结果可视化,比如生成 HTML 文件。 具体调用链不一一展开看,只看很关键的环节。首先 ClassAnalyzer 中的 processClass 方法使用了 ASM 将 class 文件解析为各种符号,保存在 ClassSymbol 对象里。// org/eclipse/emt4j/analysis/analyzer/ClassAnalyzer.java
protected static void processClass(byte[] classFileContent ...) {
ClassSymbol symbol = ClassInspectorInstance.getInstance().getSymbolInClass(classFileContent);
...
}
然后,由各种规则文件利用这些符号信息做判断,比如 WholeClassRule,表示需要整个 class 文件信息才能做判断的规则。@RuleImpl(type = "whole-class")
public class WholeClassRule extends ExecutableRule {
...
@Override
protected CheckResult check(Dependency dependency) {
Map<String, Object> mvelMap = new HashMap<>();
mvelMap.put("typeSet", dependency.getClassSymbol().getTypeSet());
mvelMap.put("methodSet", toMethodIdentifierSet(dependency.getClassSymbol().getCallMethodSet()));
mvelMap.put("cpSet", dependency.getClassSymbol().getConstantPoolSet());
Object result = MVEL.eval(mvel2Rule, mvelMap);
if (result instanceof Boolean) {
return ((Boolean) result) ? CheckResult.FAIL : CheckResult.PASS;
} else {
throw new JdkMigrationException("Mvel2 rule file" + mvel2RuleFile + " must return a boolean result!Now result type is : " + result.getClass());
}
}
}
这里将刚刚的符号信息取出来,放入 mvelMap 中,分别是 typeSet 类型集合、methodSet 方法集合、cpSet 常量池集合。 接下来使用 MVEL 这个第三方类库进行判断,这是一个可以使用表达式进行匹配判断的工具类,很方便,规则表达式就写在这个 mvel2Rule 里。 这个 mvel2Rule 有很多,其中检查 java.version 这个兼容性问题的表达式写在下面这个文件里。// emt4j-common/src/main/resources/default/rule/8to11/data/mvel2-rule-getjavaversion.cfg
methodSet.contains('java.lang.System.getProperty') &&
(
cpSet.contains('java.version') ||
cpSet.contains('java.specification.version') ||
cpSet.contains('java.runtime.version')
)
这个规则表达式很好理解,你不用了解它的写法也能看懂,就是当使用了 System.getProperty 方法,并且字符串常量池中有 java.version 或 java.specification.version 或 java.runtime.version 时,就视作有兼容性问题。 可以看出,这个判断非常粗糙,就是简单的字符串包含判断而已,由此可见,兼容性问题的判断,也逃脱不了这种方式,不要以为里面利用了什么智能分析方法。 当这个兼容性问题被记录下来后,最终输出 HTML 文件的时候,会通过 ResourceBundle 查看官方文档说明,将详细信息写入 HTML 文件中。 最终就看到了效果,就是这么简单。
- EOF -
推荐阅读 点击标题可跳转看完本文有收获?请转发分享给更多人
关注「ImportNew」,提升Java技能
点赞和在看就是最大的支持❤️