1. 为什么Java 17依然是当下企业级开发的“定海神针”?
如果你最近在搭建新的Java项目,或者维护一个老系统,大概率会听到一个建议:“用Java 17吧”。这个在2021年9月发布的长期支持版本,发布至今已经快三年了,但它的热度不仅没降,反而在开发社区和企业中的采用率持续攀升。这背后其实反映了一个非常现实的问题:对于追求稳定、长期维护和现代化特性的团队来说,Java 17在功能、性能和生态支持上,找到了一个近乎完美的平衡点。它不像Java 8那样“年事已高”,面临安全补丁和社区支持逐渐减弱的风险;也不像Java 21这样的最新版本,虽然特性炫酷,但需要时间让整个生态链(框架、中间件、云服务商)完全跟上。Java 17就像一个经验丰富、技术全面且值得信赖的“中坚力量”,它集成了Java 8之后多个版本的核心精华,并且作为Oracle指定的长期支持版本,能提供长达数年的官方更新和支持,这对于任何严肃的生产环境来说,都是至关重要的“定心丸”。
我自己在多个从Java 8或11升级到17的项目中,最直观的感受就是:升级过程比想象中平滑,但带来的收益却非常实在。无论是启动速度的优化、内存使用的改进,还是像Records、Sealed Classes这些新语法带来的代码简洁性和安全性提升,都让开发和维护体验上了一个台阶。更重要的是,现在几乎所有主流的云原生基础设施、微服务框架和构建工具,都将Java 17作为首要或默认的推荐版本。这意味着选择Java 17,你几乎不会在技术选型上遇到兼容性“踩坑”的问题。接下来,我就结合自己从下载安装到实际项目落地的完整经验,为你拆解Java 17的核心价值、避坑指南和实战技巧。
2. 深入解析Java 17的核心特性与选型逻辑
在决定使用Java 17之前,我们必须搞清楚它到底带来了什么,以及为什么它比之前的某些版本更适合作为生产环境的基准线。这不仅仅是看版本号的高低,而是要理解其作为长期支持版本的战略定位和实际技术价值。
2.1 LTS版本的战略意义与生命周期
Java 17的全称是Java SE 17,它是一个LTS版本。LTS是“长期支持”的缩写,这是Oracle在调整Java发布模型后引入的关键概念。简单来说,Oracle现在每半年发布一个功能版本,但只有特定的版本会被指定为LTS,获得长达数年的官方支持(包括关键漏洞修复和安全更新)。非LTS版本的支持期通常只有六个月。对于企业而言,这意味着选择非LTS版本,你可能会面临每隔半年就要被迫升级一次的窘境,否则系统将暴露在安全风险之下。而选择LTS版本,如Java 17,你可以获得一个稳定的、可长期维护的基础。
根据Oracle的支持路线图,针对Java 17的免费公共更新至少持续到2029年9月。这为企业规划技术栈提供了长达数年的确定性。相比之下,Java 8虽然经典,但其免费的公共更新早已结束,继续使用意味着要么付费购买商业支持,要么自行承担安全风险。因此,从生命周期管理的角度看,从Java 8或11迁移到17,是一个必然的、降低长期运维风险的举措。
2.2 不容错过的五大核心语言特性
Java 17包含了自Java 11以来多个版本引入的数百项增强,其中一些语言特性的加入,实实在在地改变了我们的编码方式。这里我重点分享五个我认为最具生产力的特性。
1. 记录类:告别样板代码的利器记录类是Java 14中首次预览、在Java 16中正式引入的特性。它旨在充当不可变数据的透明载体。以前,我们要定义一个纯粹用来存储数据的类,比如一个User,需要手动编写构造函数、getter、equals()、hashCode()和toString()方法,虽然IDE能生成,但代码非常冗长。现在,一行代码就能搞定:
public record User(String name, String email) { }这行代码自动为我们提供了:
- 一个包含所有字段的规范构造函数。
- 每个字段的
final访问器方法(如name()、email())。 - 自动生成的
equals()、hashCode()和toString()方法。 我在处理DTO、API响应对象、配置参数等场景时大量使用记录类,代码简洁性提升巨大,而且由于其不可变性和值语义,也减少了潜在的bug。
2. 密封类:构建更安全的类层次结构密封类是Java 15预览、Java 17正式推出的特性。它允许你精确控制哪些类或接口可以继承或实现它。这为建模领域概念提供了更强的约束。例如,定义一个表示“形状”的密封接口:
public sealed interface Shape permits Circle, Rectangle, Triangle { double area(); }然后,只有Circle、Rectangle、Triangle这三个类被允许实现Shape接口。编译器会强制检查,如果你尝试添加一个未在permits列表中声明的类来实现Shape,编译将失败。这在与switch表达式模式匹配结合时尤其强大,因为编译器可以知道所有可能的子类型,从而实现穷尽性检查,避免遗漏分支。这对于构建领域驱动设计中的核心模型非常有用,能有效防止模型在后续维护中被意外破坏。
3. 文本块:处理多行字符串的优雅方式文本块是Java 13预览、Java 15正式推出的特性。它极大地简化了JSON、XML、SQL或HTML等多行字符串的编写。以前我们需要用一堆加号和转义符来拼接,现在只需要用三个双引号界定:
String json = """ { "name": "张三", "age": 30, "city": "北京" } """;文本块会自动处理缩进和换行,使得代码中的嵌入数据清晰可读,维护起来也方便得多。
4. 模式匹配:简化对象检查和提取instanceof的模式匹配(Java 16正式引入)和switch表达式/语句的模式匹配(Java 17中预览,后续版本加强)是另一项提升代码简洁性的特性。传统的instanceof检查后通常需要强制转型:
if (obj instanceof String) { String s = (String) obj; // 使用s }现在可以写成:
if (obj instanceof String s) { // 直接使用s }变量s在条件为真的分支内自动被绑定并转型,减少了样板代码和出错的几率。
5. 强封装内部API:迈向更安全的模块化Java 17进一步加强了对于内部API(如sun.misc.Unsafe)的封装。默认情况下,许多在Java 9之前可以自由使用的内部API现在无法通过反射直接访问了。这可能会影响一些深度依赖这些API的旧库(比如某些字节码操作库或序列化框架)。虽然可以通过启动参数--add-opens来临时打开访问,但官方意图是推动生态库迁移到标准的、稳定的API上。从长远看,这提升了Java平台的安全性和稳定性。在升级时,如果遇到Illegal reflective access警告,就需要检查并更新相关依赖。
2.3 性能与垃圾回收器的持续优化
除了语言特性,Java 17在底层性能上也有显著提升。HotSpot虚拟机持续在即时编译、内存管理和垃圾回收方面进行优化。对于大多数应用,升级到Java 17都能获得“免费”的性能提升,尤其是启动时间和吞吐量方面。
在垃圾回收器方面,ZGC和Shenandoah这两个低延迟GC在Java 17中已经是非常成熟的生产就绪特性。ZGC的目标是在任意堆大小下都将停顿时间控制在10毫秒以内,且对吞吐量影响极小。如果你的应用对响应延迟有苛刻要求(如金融交易、实时游戏),那么搭配Java 17使用ZGC是一个绝佳选择。启用非常简单,只需在启动参数中加入-XX:+UseZGC即可。Shenandoah GC也有类似的目标,其实现机制略有不同。G1 GC作为默认回收器,其性能和可预测性也在持续改进。
注意:选择ZGC或Shenandoah时,需要确保你的操作系统和硬件支持。例如,ZGC在Linux上支持最完善,在macOS和Windows上作为实验性功能提供。生产环境部署前务必在对应环境下进行充分的压力测试。
3. 从零开始:多平台安装与环境配置实战
了解了Java 17的价值,下一步就是把它装到你的开发机或服务器上。虽然听起来简单,但不同的平台和不同的安装方式,有一些细节需要注意,否则可能会遇到环境变量冲突、版本管理混乱等问题。
3.1 官方下载渠道与版本选择要点
最直接的来源是Oracle官网。正如搜索内容所示,Oracle提供了详细的归档下载页面。这里有一个关键信息:对于Java 17,从17.0.13版本开始,许可证条款发生了变化。17.0.12及之前的版本使用“Oracle No-Fee Terms and Conditions”许可证,允许免费用于商业生产。而17.0.13及之后的版本,则需遵循新的“Oracle No-Fee Terms and Conditions”或“Oracle Technology Network License Agreement”,对于某些生产用途可能需要付费订阅。因此,对于个人学习、开发或某些特定场景,许多人会选择下载17.0.12这个最后的“免费商用”更新版本。但请注意,Oracle明确警告:旧版本不包含最新的安全补丁,不建议用于生产。
对于生产环境,我的建议是:
- 使用最新的LTS版本:如果项目允许,直接使用Java 21(下一个LTS)或等待Java 23(LTS)。它们包含了更多新特性和安全修复。
- 如果锁定Java 17:考虑使用其他提供免费长期支持的发行版,如Eclipse Temurin(Adoptium)、Amazon Corretto、Microsoft Build of OpenJDK或Azul Zulu。这些发行版都基于OpenJDK源码构建,提供了对Java 17的免费、高质量更新,是生产环境的更优选择。
以最流行的Eclipse Temurin为例,其官网提供了清晰的下载链接和安装指南。接下来的实操,我将以在Linux和macOS上安装Temurin的Java 17为例,因为这是最常见的服务器和开发环境。
3.2 Linux系统安装与管理(以Ubuntu/CentOS为例)
在Linux服务器上,我强烈推荐使用压缩包安装,而非包管理器自带的版本。这让你对Java版本有完全的控制权,方便多版本共存和切换。
步骤一:下载并解压首先,访问Eclipse Temurin官网,找到Java 17的Linux x64压缩包链接。使用wget或curl下载。
# 创建一个专门的目录存放Java sudo mkdir -p /usr/lib/jvm cd /usr/lib/jvm # 下载Temurin JDK 17 (请替换为官网最新的具体链接) sudo wget https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.12%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.12_7.tar.gz # 解压 sudo tar -xzf OpenJDK17U-jdk_x64_linux_hotspot_17.0.12_7.tar.gz # 可以删除压缩包 sudo rm OpenJDK17U-jdk_x64_linux_hotspot_17.0.12_7.tar.gz解压后,你会得到一个类似jdk-17.0.12+7的目录。
步骤二:配置系统环境变量为了能在任何位置使用java和javac命令,需要配置JAVA_HOME并更新PATH。
# 编辑profile文件,这里使用/etc/profile.d/下的自定义脚本,更清晰 sudo vim /etc/profile.d/java17.sh在文件中添加以下内容:
export JAVA_HOME=/usr/lib/jvm/jdk-17.0.12+7 # 请确保路径与你解压的目录名一致 export PATH=$JAVA_HOME/bin:$PATH保存退出后,使配置生效:
source /etc/profile.d/java17.sh现在,打开一个新的终端,输入java -version和javac -version,应该能看到Temurin 17的信息。
步骤三:配置系统默认Java(可选)如果系统上安装了多个Java(如自带的OpenJDK 11),你可以使用update-alternatives来管理默认版本。
sudo update-alternatives --install /usr/bin/java java $JAVA_HOME/bin/java 1000 sudo update-alternatives --install /usr/bin/javac javac $JAVA_HOME/bin/javac 1000 # 如果需要,可以通过以下命令交互式选择 # sudo update-alternatives --config java3.3 macOS系统安装与配置
在macOS上,除了下载.dmg安装包图形化安装外,我更推荐使用Homebrew进行管理,这是最便捷的方式。
通过Homebrew安装:
# 首先,确保Homebrew已更新 brew update # 搜索可用的JDK版本 brew search openjdk # 安装Temurin的JDK 17 brew install --cask temurin安装完成后,Java会被安装到/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home。Homebrew通常会自动为你配置好环境变量。你可以通过/usr/libexec/java_home命令来管理多个JDK版本。
手动配置环境变量:如果Homebrew没有自动配置,或者你想手动指定,可以编辑你的shell配置文件(如~/.zshrc)。
vim ~/.zshrc添加:
export JAVA_HOME=$(/usr/libexec/java_home -v 17) export PATH=$JAVA_HOME/bin:$PATH然后执行source ~/.zshrc。
3.4 Windows系统安装要点
在Windows上,从Oracle或Temurin官网下载.msi或.exe安装程序是最简单的方式。安装过程基本是“下一步”到底。关键步骤在于安装后的环境变量配置。
- 安装完成后,打开“系统属性” -> “高级” -> “环境变量”。
- 在“系统变量”部分,点击“新建”,变量名填
JAVA_HOME,变量值填你的JDK安装路径,例如C:\Program Files\Eclipse Adoptium\jdk-17.0.12.7-hotspot。 - 找到“系统变量”中的
Path变量,双击编辑,点击“新建”,添加%JAVA_HOME%\bin。 - 依次点击确定保存。打开新的命令提示符,输入
java -version验证。
实操心得:无论在哪个平台,安装后务必在终端或CMD中运行
java -version和javac -version来双重验证。有时java命令可能指向了旧的JRE,而javac没有正确配置。确保两者都来自你新安装的JDK 17。
4. 项目迁移与构建工具适配指南
将现有项目升级到Java 17,或新建一个基于Java 17的项目,需要构建工具和依赖库的配合。这里以最主流的Maven和Gradle为例。
4.1 Maven项目配置
在Maven的pom.xml中,你需要配置三个关键地方:编译器版本、源代码版本和打包插件。
1. 配置Maven编译器插件:这是最重要的一步,告诉Maven使用Java 17来编译代码。
<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <!-- 使用较新版本以更好支持新特性 --> <configuration> <source>17</source> <target>17</target> <!-- 如果需要启用预览特性,添加此配置 --> <!-- <compilerArgs>--enable-preview</compilerArgs> --> </configuration> </plugin> </plugins> </build>2. 配置Maven打包插件(如Spring Boot):如果你使用Spring Boot,其Maven插件需要与Java 17兼容。确保使用2.7.x或3.x版本。
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.7.18</version> <!-- 或3.x版本 --> </plugin>4.2 Gradle项目配置
在Gradle中,配置更为简洁。在build.gradle或build.gradle.kts文件中进行设置。
Groovy DSL (build.gradle):
plugins { id 'java' } java { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } tasks.withType(JavaCompile).configureEach { options.compilerArgs += '--enable-preview' // 如需启用预览特性 }Kotlin DSL (build.gradle.kts):
plugins { java } java { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } tasks.withType<JavaCompile>().configureEach { options.compilerArgs.add("--enable-preview") }4.3 依赖库兼容性检查与升级
这是迁移过程中最可能遇到问题的一环。一些较老的库可能不完全兼容Java 17,尤其是涉及深度反射、字节码操作或使用了被强封装内部API的库。
常见需要关注的库:
- ASM, CGLIB, ByteBuddy:字节码操作库,通常需要较新版本。
- Lombok:确保使用1.18.24或更高版本。
- 各种连接池、监控代理:如Druid, HikariCP, SkyWalking Agent等。
- 序列化框架:如Kryo, FST。
- 测试框架:JUnit 5, Mockito等通常兼容性很好,但建议使用最新稳定版。
排查方法:
- 运行时警告:启动应用时,注意控制台是否有
WARNING: Illegal reflective access by ...之类的警告。这通常意味着某个库在访问被封装的内部API。虽然应用可能暂时能运行,但这是潜在的风险点,在未来版本中可能被完全禁止。 - 依赖检查:使用Maven命令
mvn dependency:tree或Gradle命令gradle dependencies查看所有依赖及其传递依赖。尝试将所有依赖升级到已知支持Java 17的最新版本。 - 社区与Issue:在GitHub上搜索你所用库的Issue,关键词“Java 17”或“JDK 17”,查看是否有已知问题和解决方案。
踩坑记录:我曾在一个项目中遇到使用旧版本
spring-cloud-starter-netflix-eureka-client导致启动失败的问题。原因是其底层依赖的JAXB模块在Java 11+中默认不再包含。解决方案不是降级Java,而是显式添加依赖:<dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.1</version> </dependency>这类问题非常典型,升级时要有心理准备。
5. 生产环境部署、监控与问题排查实录
将基于Java 17的应用部署到生产环境,除了常规的发布流程,还需要针对新版本的特点进行一些优化和监控。
5.1 容器化部署最佳实践
如今Docker+Kubernetes是主流。为Java 17应用构建Docker镜像时,有几点需要注意:
1. 选择合适的基础镜像:避免使用庞大的openjdk:17完整镜像。优先选择eclipse-temurin:17-jre或更小的eclipse-temurin:17-jre-alpine作为运行时镜像。在构建阶段使用JDK镜像,最终镜像只包含JRE。
# 多阶段构建示例 FROM eclipse-temurin:17-jdk AS builder WORKDIR /app COPY . . RUN ./mvnw clean package -DskipTests FROM eclipse-temurin:17-jre WORKDIR /app COPY --from=builder /app/target/*.jar app.jar # 建议非root用户运行 USER 1001 ENTRYPOINT ["java", "-jar", "app.jar"]2. 合理配置JVM参数:在容器中运行Java,内存和CPU是受限的。务必设置合理的堆内存参数。
ENTRYPOINT ["java", \ "-Xms512m", \ "-Xmx512m", \ "-XX:+UseContainerSupport", \ "-XX:MaxRAMPercentage=75.0", \ "-jar", "app.jar"]-Xms512m -Xmx512m:设置初始和最大堆内存。在容器中,建议两者设为相同值,避免堆内存动态调整带来的性能开销。-XX:+UseContainerSupport:让JVM感知到容器资源限制(对于较新的JDK版本,此参数可能已默认启用或更名为-XX:+UseContainerCpuShares等,但加上无害)。-XX:MaxRAMPercentage=75.0:设置JVM堆最大可用内存占容器总内存的百分比。这是一个比固定-Xmx更灵活的方式,能更好地适应容器内存配额的变化。这里设置为75%,为堆外内存(如元空间、线程栈、直接缓冲区等)留出空间。
5.2 监控与诊断工具链
Java 17提供了更多内置的监控和诊断能力,同时需要与外部监控体系集成。
1. 充分利用JFR与JMC:Java Flight Recorder现在对于OpenJDK发行版也是免费的了。它是一个低开销的性能事件收集器。你可以在启动时开启JFR,并将记录文件导出进行分析。
java -XX:StartFlightRecording=duration=60s,filename=myrecording.jfr -jar app.jar使用Java Mission Control或更现代的JDK Mission Control来分析.jfr文件,可以深入了解方法性能、锁竞争、GC活动、内存分配等。
2. 健康检查与指标暴露:在微服务架构中,确保应用提供健康检查端点(如Spring Boot Actuator的/actuator/health)和指标端点(如/actuator/prometheus)。监控系统(如Prometheus+Grafana)可以收集JVM内存、GC时间、线程状态等关键指标。
3. GC日志分析:开启详细的GC日志对于排查内存问题和性能调优至关重要。
java -Xlog:gc*,gc+heap=debug,gc+age=trace:file=gc.log:time,uptime,level,tags:filecount=10,filesize=10m -jar app.jar这个参数会输出详细的GC日志到文件,并滚动保存。可以使用GC日志分析工具(如GCeasy, GCE Viewer)或自己编写脚本分析,关注Full GC的频率和持续时间。
5.3 常见问题排查速查表
在运维Java 17应用时,你可能会遇到一些新版本特有的或常见的问题。这里我整理了一个速查表:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
应用启动失败,报错UnsupportedClassVersionError | 编译版本高于运行环境JDK版本。 | 1. 运行java -version确认环境JDK版本≥17。2. 检查构建工具配置,确保 sourceCompatibility和targetCompatibility设置正确。 |
启动时大量Illegal reflective access警告 | 第三方库通过反射访问了Java内部API。 | 1. 根据警告信息定位问题库。 2. 升级该库到最新版本。 3. 若无法升级,可尝试添加JVM参数 --add-opens临时开放模块(生产环境慎用,需评估安全风险)。 |
| 容器中应用内存使用超出限制被OOM Kill | JVM堆内存设置过大,未考虑堆外内存。 | 1. 检查容器内存限制。 2. 使用 -XX:MaxRAMPercentage替代固定-Xmx值,例如设置为70%-75%。3. 监控堆外内存使用(如Native Memory Tracking)。 |
启用ZGC后,出现Unrecognized VM option 'UseZGC' | ZGC在该平台或JDK构建中不可用。 | 1. 确认使用的是支持ZGC的JDK发行版(如Temurin)。 2. 在Linux上,ZGC支持最完善。macOS/Windows上可能需特定版本或作为实验特性启用( -XX:+UnlockExperimentalVMOptions -XX:+UseZGC)。 |
使用Records或Sealed Classes编译失败 | 编译器级别设置不正确。 | 1. 确保Maven编译器插件版本≥3.8.0,并正确设置了<source>17</source>。2. 对于Gradle,确保 sourceCompatibility = 17。 |
| 应用性能无明显提升甚至下降 | 新版本GC策略或默认参数不适应应用负载。 | 1. 收集并分析GC日志,观察GC停顿时间和频率。 2. 尝试切换或调整GC参数(如从G1切换到ZGC,或调整G1的 MaxGCPauseMillis)。3. 使用JFR录制一段时间,分析性能热点。 |
一个真实案例:我们有一个批处理应用迁移到Java 17后,在压力测试下出现了周期性的停顿。通过分析JFR记录,发现是G1垃圾回收器的并发标记阶段耗时较长。由于该应用内存占用较大(堆约16G),我们尝试切换到ZGC,通过添加-XX:+UseZGC -Xmx16g -Xms16g参数,停顿时间从原来的几百毫秒降低到了十毫秒以内,吞吐量损失也在可接受范围内。这个调整过程的关键就在于基于数据的分析,而不是盲目猜测。
6. 面向未来的开发建议与生态展望
拥抱Java 17不仅仅是升级一个运行时,更是拥抱一套更现代、更高效的开发范式。基于我的经验,给正在或计划使用Java 17的团队一些建议。
首先,在代码层面积极采用新特性。不要仅仅满足于“能跑”。在新代码中,果断使用Records来替代那些只有getter/setter的贫血模型;用Sealed Classes来设计更安全的领域模型和状态机;用Text Blocks让SQL或JSON字符串变得清晰。这些特性能显著提升代码的可读性、可维护性和安全性。对于模式匹配,可以从instanceof模式匹配开始,逐步在条件逻辑复杂的代码中应用。
其次,建立与Java新版本节奏同步的升级文化。Java的快速发布节奏要求团队不能像过去那样,一个版本用五到十年。建议建立一个机制,定期(如每半年或一年)评估下一个Java版本的新特性,并在开发环境中进行小范围试用。这样,当需要升级到下一个LTS时(比如从17到21),整个过程会平滑很多,而不是一次性地面对海量变化。
再者,关注云原生和GraalVM Native Image。Java 17是构建云原生应用和探索GraalVM原生镜像编译的绝佳起点。Spring Boot 3.x、Quarkus、Micronaut等现代框架都对Java 17+和GraalVM提供了强力支持。将应用编译为原生可执行文件,可以带来极快的启动速度和更低的内存占用,非常适合Serverless和容器环境。虽然这项技术还有一些限制(如反射、动态代理需要额外配置),但无疑是Java生态的一个重要发展方向。
最后,我想说的是,技术选型没有银弹。Java 17对于绝大多数寻求稳定、现代化和长期支持的企业级应用来说,是目前综合最优的选择。它平衡了创新与稳定,拥有强大的生态和工具链支持。升级的过程或许会遇到一些小麻烦,但由此带来的开发体验提升、运行时性能改进以及为未来技术演进铺平的道路,绝对是值得投入的。我自己的项目在完成升级后,团队对新语法特性的接受度很高,代码库也变得更加简洁清晰,那些升级过程中解决的兼容性问题,也让团队对应用的依赖和运行机制有了更深的理解。这或许就是技术升级带来的额外收获吧。