ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Windows下JDK多版本管理:PowerShell一键安装与秒切版本

Windows下JDK多版本管理:PowerShell一键安装与秒切版本 作为一个天天和 Java 打交道的开发我最大的感受就是Windows 上管理多个 JDK 版本比写业务代码还折磨人。项目 A 必须用 JDK 8项目 B 得切到 JDK 17新项目又想尝尝 JDK 21 的特性。每次切换都要打开系统属性改 JAVA_HOME改 Path还要担心新开的命令行窗口到底有没有读到新环境变量。装了多个 JDK 之后安装包自带的“配置环境变量”选项还会把系统 PATH 搞得一团糟最后java -version输出的永远不是你想要的那个版本。所以我自己整理了一套 Windows 下的 JDK 多版本管理方案核心就两个动作用 PowerShell 脚本一键安装指定版本的 JDK然后一条命令秒切版本。这篇文章会把这套方案的完整思路、完整脚本、实测过程和避坑经验都写清楚适合所有在 Windows 上做 Java 开发、被 JDK 版本切换折磨过的人。1. 为什么 Windows 下的 JDK 多版本管理这么麻烦1.1 不同项目对 JDK 版本的硬性要求Java 生态有个比较特殊的地方不同框架和工具链对 JDK 版本的要求相当严格。很多遗留系统跑在 JDK 8 上因为它们用了 javax 包、老的第三方库换到 JDK 17 很可能直接编译不过。而 Spring Boot 3 又强制要求 JDK 17 起步部分新组件甚至鼓励用 JDK 21。Gradle、Maven 插件也存在版本兼容矩阵我见过不少同事在切换项目后第一件事就是重新配置 IDE 里的 JDK。问题在于你会同时维护多个项目。今天修一个老年人项目的 bug明天开发新服务后天可能还要跑一个临时脚本。每个项目都指定自己的 JDK 版本指望着“装一个最新版搞定所有事”基本不可能。装上多个版本之后又得想办法让系统能区分、能快速切换。1.2 手动改环境变量的三大痛点最传统的做法就是手动改环境变量把 JAVA_HOME 指到目标 JDK 目录再把 Path 里的%JAVA_HOME%\bin提前。听起来简单实际用起来有不少坑。第一个坑是容易改错。JDK 的安装目录通常带版本号比如C:\Program Files\Java\jdk-17.0.12手滑写错一个字符环境变量立刻失效。而且某些安装包会在 Path 里同时塞入C:\Program Files\Common Files\Oracle\Java\javapath这种系统路径它经常排在%JAVA_HOME%\bin前面导致你用java -version查到的版本根本不是自己设置的。第二个坑是生效延迟。Windows 环境变量修改后已经打开的命令行窗口不会自动感知。哪怕你用了setx新开的 cmd 窗口也未必能拿到最新值经常要重启终端甚至注销登录。为了切个版本反复重启 IDE、重启终端非常浪费时间。第三个坑是环境不一致。你在 IDE 里能选 SDK在命令行里又依赖 JAVA_HOME两个地方可能配置成不同版本。于是经常出现“IDE 里编译正常命令行 mvn 打包却失败”的诡异现象排查起来相当恼火。1.3 现有方案和工具的局限很多人会想到用工具来解决但 Windows 上的选择确实不多。Linux 下的 SDKMAN 很好用Windows 却不支持。Scoop 可以安装 JDK但它本身也是一套包管理生态还要额外学习和维护。Chocolatey 能装软件但大多数安装包默认使用 MSI 安装器依然会写系统环境变量。IDE 里的 SDK 切换只对 IDE 自身有效出不了 IDE 就管不着。用 Docker 容器跑 Java 也不是不行但本地开发改代码、跑测试、看日志都要经过容器中间多一层转换调试体验并不好。我更倾向于拿回“管理 JDK 本身”的控制权把 JDK 当作普通的绿色软件用脚本统一管理想切就切。2. 方案设计思路为什么选 PowerShell 本地目录部署2.1 核心思路目录化 JDK 存储 用户级环境变量动态切换我这套方案的核心思想很简单把不同版本的 JDK 全部解压到同一个目录下统一命名然后通过修改用户级环境变量来实现版本切换。不需要管理员权限不碰系统变量也不写注册表。具体来说所有 JDK 都放在%USERPROFILE%\jdks目录下每个版本一个子目录比如jdk-8、jdk-17、jdk-21。切换版本时只需要把用户级 JAVA_HOME 指到对应子目录再把 Path 中的%JAVA_HOME%\bin调整到最前面新开的终端窗口就会读到新的 JDK。所有操作都封装成一个 PowerShell 函数一条命令完成。这样设计的好处非常明显。第一所有 JDK 都在一个目录里删一个版本就是删一个文件夹干干净净。第二切换只改环境变量不卸载不重装速度快。第三因为改的是用户变量不需要管理员权限在公司电脑上也能用。第四目录本身就是可以复制的换电脑时直接把jdks文件夹拷走环境就能快速恢复。2.2 为什么不用安装包而用 zip 包JDK 官方网站一般提供安装包和解压包两种形态。安装包最大的问题它会在系统里创建卸载记录、写注册表、设置系统 Path甚至会偷偷加入自动更新任务。对于本地开发机来说这些“附加功能”不仅没必要还会干扰多版本管理。而 zip 包是纯绿色形态解压后就是一个完整的 JDK 目录。我平时下载用的主要是 Adoptium 发行的 Temurin JDK它提供 Windows zip 包无登录、无广告下载地址还是固定 API非常适合脚本调用。Temurin 也是目前社区很常用的 OpenJDK 发行版兼容性不错。有的版本解压后目录名带版本号和构建号例如jdk-17.0.127这不利于统一管理。所以脚本里会在安装完成后自动重命名成jdk-17这种稳定名称切换命令只认稳定名称。这也是不用安装包的一个重要原因安装包的安装路径不可控而 zip 包的目录名我们自己说了算。2.3 为什么用 PowerShell 而不是 .bat / CMDWindows 下做环境变量管理PowerShell 比传统批处理顺手太多了。批处理处理字符串很强但涉及到下载、解压、操作注册表级别的用户环境变量时就比较别扭。PowerShell 能直接调用 .NET API比如[Environment]::SetEnvironmentVariable可以明确指定写入 User 级别还是 Machine 级别非常可靠。另一个原因是 PowerShell 天生适合做“命令行工具”。我可以定义函数jdk install 17、jdk use 17让用户像用 Linux 包管理器一样操作。凡是会写一点 PowerShell 的人都能轻松读懂和修改脚本。而且 PowerShell 是 Windows 自带的不用额外安装运行时默认 5.1 版本就够用。还有一点PowerShell 函数具有当前会话上下文。我可以在脚本中刷新当前进程的$env:Path让切换后立刻在当前窗口运行java -version验证结果。批处理很难做到这种“即时体验”。3. 完整脚本一键安装 秒切版本3.1 脚本功能一览整个脚本封装了三个核心操作全部通过jdk命令入口调用功能清单如下命令功能示例jdk list列出%USERPROFILE%\jdks下已安装的 JDK 版本并显示版本信息jdk listjdk install 版本号从 Adoptium 下载指定版本的 JDK自动解压并统一目录名jdk install 17jdk use 版本号将用户级 JAVA_HOME 和 Path 切换到指定版本并刷新当前终端环境变量jdk use 17jdk 版本号直接切换的快捷方式jdk 8安装命令默认安装该大版本下最新的 GA 版本例如jdk install 17会安装最新的 JDK 17 更新。因为是从 Adoptium API 下载所以不需要自己去找下载链接。3.2 完整 PowerShell 脚本代码直接复制以下内容保存为jdk-switch.ps1# jdk-switch.ps1 # 用法先执行 . .\jdk-switch.ps1 加载函数再执行 jdk 命令 $JdkRoot Join-Path $env:USERPROFILE jdks function Get-JdkInstalled { if (-not (Test-Path $JdkRoot)) { Write-Host 还没有安装任何 JDK先运行 jdk install 版本号 return } $list Get-ChildItem -Path $JdkRoot -Directory | Where-Object { $_.Name -match ^jdk-\d } | ForEach-Object { $javaBin Join-Path $_.FullName bin\java.exe $versionText if (Test-Path $javaBin) { ( $javaBin -version 21 | Select-Object -First 1).ToString().Trim() } else { 无效安装 } [PSCustomObject]{ 目录 $_.Name; 版本信息 $versionText } } if ($list) { $list | Format-Table -AutoSize } else { Write-Host JDK 目录下没有可用版本先运行 jdk install 版本号 } } function Install-Jdk { param([int]$Version) if ($Version -lt 8 -or $Version -gt 24) { Write-Host 暂不支持这个版本建议使用 8 到 24 之间的 LTS 版本。 return } $JdkHome Join-Path $JdkRoot jdk-$Version if (Test-Path $JdkHome) { if (Test-Path (Join-Path $JdkHome bin\java.exe)) { Write-Host JDK $Version 已安装目录$JdkHome } else { Write-Warning 检测到目录 $JdkHome 但其中没有 bin\java.exe脚本不会覆盖。请手动删除后再安装。 } return } $existingDirs Get-ChildItem -Path $JdkRoot -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match jdk.*$Version } if ($existingDirs) { $existing $existingDirs | Select-Object -First 1 $target Join-Path $JdkRoot $existing.Name if ($target -ne $JdkHome) { Rename-Item $existing.FullName $JdkHome -Force } Write-Host 检测到已下载的目录已重命名为 $JdkHome return } New-Item -ItemType Directory -Force -Path $JdkRoot | Out-Null $tempZip Join-Path $env:TEMP jdk-$Version.zip $apiUrl https://api.adoptium.net/v3/binary/latest/$Version/ga/windows/x64/jdk/hotspot/normal/eclipse Write-Host 正在从 Adoptium 下载 JDK $Version请稍候... try { Invoke-WebRequest -Uri $apiUrl -OutFile $tempZip -UseBasicParsing -MaximumRedirection 5 } catch { Write-Error 下载失败$($_.Exception.Message) Remove-Item $tempZip -Force -ErrorAction SilentlyContinue return } Write-Host 下载完成开始解压到 $JdkRoot ... Expand-Archive -Path $tempZip -DestinationPath $JdkRoot -Force Remove-Item $tempZip -Force $expanded Get-ChildItem -Path $JdkRoot -Directory | Where-Object { $_.Name -match jdk.*$Version -and (Test-Path (Join-Path $_.FullName bin\java.exe)) } | Select-Object -First 1 if ($expanded -and $expanded.FullName -ne $JdkHome) { Rename-Item $expanded.FullName -NewName jdk-$Version -Force } if (Test-Path (Join-Path $JdkHome bin\java.exe)) { Write-Host JDK $Version 安装成功目录$JdkHome } else { Write-Error 安装失败解压后找不到 java.exe请检查网络源或手动下载 zip 包放到 $JdkRoot 下。 } } function Set-Jdk { param([int]$Version) $JdkHome Join-Path $JdkRoot jdk-$Version if (-not (Test-Path (Join-Path $JdkHome bin\java.exe))) { Write-Error 没有检测到 JDK $Version先执行jdk install $Version return } [Environment]::SetEnvironmentVariable(JAVA_HOME, $JdkHome, User) $userPath [Environment]::GetEnvironmentVariable(Path, User) if ($null -eq $userPath) { $userPath } $paths $userPath -split ; | Where-Object { $item $_.Trim() if ([string]::IsNullOrEmpty($item)) { return $false } if ($item -like *$JdkRoot*) { return $false } if ($item -like *%JAVA_HOME%*) { return $false } return $true } $newUserPath ($paths -join ;).TrimEnd(;) ;%JAVA_HOME%\bin [Environment]::SetEnvironmentVariable(Path, $newUserPath, User) $env:JAVA_HOME $JdkHome $machinePath [Environment]::GetEnvironmentVariable(Path, Machine) if (-not $machinePath) { $machinePath
返回列表