高效切换JDK版本的技巧和原理

Posted on 2023-11-30

背景

Java语言平台的演进一贯十分重视兼容性,这点从Java泛型特性的实现就可见一斑:为了保证陈年老代码也可以运行在支持泛型的JVM平台上,权衡、取舍过后,泛型的设计者们最终选择在编译期来实现类型泛化能力,虽然说这种实现在某些场景下的理解成本、使用成本很高,但是在jdk1.1环境写的业务代码在jdk1.7环境上啥也不改就可以正常跑,你就说兼容性好不好吧?

20240928172431920

但是在云原生思想大行其道的今天,Java语言平台的市场份额不断被各类新兴语言侵蚀。为了应对这个严峻的挑战,维护Java语言平台的那群专家们不得不放弃一以贯之的稳健的版本迭代策略,开始积极的引入新特性,发布新版本,甚至不惜牺牲兼容性。

你在jdk8上跑的好好的代码,在jdk9上可不一定能直接跑了,在jdk11、jdk17等版本尤甚。因此我们再也不能像之前一样,闭着眼睛安装最新版jdk然后吭哧吭哧写代码了,因为维护Java语言平台的那群专家们想法变了T_T。

面对这个新情况,开发者们不得不在本地安装不同版本的jdk,然后根据实际情况手动切换jdk版本了。

当然,现代化的IDE,比如IDEA,会提供源码项目级别的jdk版本配置。但是,如果我们使用的命令行工具依赖了某个特定的jdk版本,那就不太好整了,常规的思路是我们自己手动修改JAVA_HOME环境变量、rehash shell

作为程序员,对于这种繁琐的人肉操作,必然是深恶痛绝的!那有没有什么其他好办法呢?

jenv应运而生~

为规避潜在的法律风险本文截图中的JDK供应商信息均马赛克脱敏

是什么

20240928172616404

jenv官网

如其官网slogan所述:jenv是一个帮助你忘记如何设置JAVA_HOME环境变量的命令行工具。

说人话就是:jenv是一个命令行工具,可以帮你快速切换jdk版本相关的各类上下文,如JAVA_HOME环境变量等。

我们可以在本地同时安装jdk8、jdk11、jdk17、jdk21等多个LTS版本,然后必要的时候,一键在上述不同的jdk版本间切换。

image-20240928174532162

如上图,我们一行命令jenv global 17就实现了jdk版本切换~
亲测下来这个工具确实蛮实用的,也很轻量。唯一美中不足的就是它的使用文档太抽象了,整个一个一言难尽。我也是摸索了好久、翻了下它的源码才搞清楚到底怎么玩。
但是不用担心,笔者负重前行就是为了让各位看官老爷们岁月静好,接下来笔者就来给大家好好介绍下安装、配置、使用。

怎么用

安装

不建议使用官网上提到的brew安装方式,亲测有坑。建议按我列的步骤来:

  1. 下载源码
git clone https://github.com/jenv/jenv.git ~/.jenv

这个源码本质上就是一堆shell脚本,直接下载到home目录下的.jenv隐藏目录下

  1. 初始化
echo 'export PATH="$HOME/.jenv/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(jenv init -)"' >> ~/.zshrc

上述命令做了两件事情:

  • jenv命令路径添加到PATH环境变量,方便后面使用
  • zsh初始化脚本(.zshrc)中注入了一些环境变量,如下图:

image-20240928173043428

  1. 激活插件
jenv enable-plugin export

image-20240928173112628

该插件用于导出JAVA_HOME环境变量,便于其他依赖jdk的命令行工具如mvn使用,需要注意这个会和jenv local命令有些冲突。 但是问题不大,个人觉得jenv local没啥用,忘了它吧,jenv global偷电瓶车养你~

到这里,jenv工具的安装就完成啦~

配置

jenv工具可以帮助我们快速切换jdk,但前提是jenv得知道本机当前存在哪些版本的jdk。
因为jdk可以安装在任意位置,所以jenv无法自动发现本机安装的jdk,所以需要我们告诉jenv,我们机器上各个jdk的安装位置。
我们需要使用jenv add命令登记本机上的jdk路径,如下图演示:

image-20240928173156093

我们首先需要定位到本机安装的jdk位置,一般mac系统通过pkg文件安装的jdk都会放到约定的目录:

/Library/Java/JavaVirtualMachines

通过tar.gz文件解压安装的jdk就得看当时安装的时候具体放在哪个位置了,一般也建议通过这种方式安装的jdk挪到上述位置~
如下图,笔者当前安装了3个版本的jdk:

image-20240928173455474

接下来就是通过jenv add命令向jenv登记本机存在的jdk了,如:

jenv add /Library/Java/JavaVirtualMachines/xxx-8.jdk/Contents/Home

image-20240928173628002

需要注意,登记的路径必须是jdkbin目录的父目录,在Mac上一般是在jdk安装目录的/Contents/Home子目录。

image-20240928173712941

MacOS特殊的Application安装机制导致存在/Contents/Home这一层路径。

通过jenv versions命令可以查看当前登记过的jdk版本:

jenv versions

image-20240928173805206

笔者已经将当前安装的4个版本jdk都完成登记,所以可以看到8、17、21等多个版本~ 接下来就是重复上述步骤,将本机当前安装的所有版本的jdk登记到jenv了,不做赘述。

切换

完成jdk登记后,后续就是使用jenv gloabl命令一键切换jdk版本了,如下图,笔者本机安装了3个版本的jdk:

image-20240928173805206

上述jenv versions命令输出的就是登记到jenv的各个jdk版本的别名,后续使用jenv global命令配合jdk版本别名,即可实现一键切换jdk版本。

如果想切换到21,则运行:jenv global 21

image-20240928173932010

如果想切换到17,则运行:jenv global 17

image-20240928173959198

完美~

版本别名

在笔者Mac上执行版本列表命令,可以看到版本别名非常规整、干净。这是手动调整过的,默认生成的版本别名就很乱七八糟。

image-20240928173805206

jenv是通过软连接来实现jdk切换的,上述jenv versions输出的版本别名列表其实是下述目录中的文件列表:

 ~/.jenv/versions

image-20240928174219125

想让版本别名更规整、干净,其实就是增删改上述文件名~懂的都懂,笔者不再赘述。

防呆

jdk版本切换太方便带来的唯一的问题就是容易犯错误。 如果我不小心一键切换到了一个jdk21,然后mvn deploy了一个jar包,其他使用jdk8的服务集成这个jar包的时候,是会报错的。因为低版本的jvm拒绝加载高版本jdk编译、生产的字节码文件~ 为了避免不小心手残导致的上述问题,可以在项目中集成enforcer插件,设置jdk版本规则,保证构建时使用的jdk版本一定符合要求~


<plugin>
    <artifactId>maven-enforcer-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>enforce</goal>
            </goals>
            <configuration>
                <rules>
                    <requireJavaVersion>
                        <message>
                            <![CDATA[当前项目只允许使用jdk8构建!]]>
                        </message>
                        <version>1.8</version>
                    </requireJavaVersion>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>

啥原理

原理说来话长,简单说就是,jenv通过PATH环境变量优先级,劫持了jdk相关命令,然后按设置将命令和命令参数重定向到执行的jdk目录。 以java命令为例:

image-20240928174303229

正确安装、配置jenv以后,我们执行的jdk相关命令,其实都是jenv导出的shell脚本,后续由jenv脚本解析、重定向相关命令。

总结

笔者基于亲身实践,从安装、使用、原理等角度深入浅出、图文并茂的介绍了一款轻便好用的jdk版本切换工具,希望能帮助到在场的所有人。