ARTICLE DETAIL

资讯详情

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

Spring Boot打包全攻略:从Fat Jar原理到Docker部署

Spring Boot打包全攻略:从Fat Jar原理到Docker部署 如果你刚把Spring Boot项目从“在IDEA里点一下Run”推进到“要打成Jar包扔到服务器上跑”这一步多半会撞上几件让人哭笑不得的事本地一切正常mvn package却报错辛苦打包成功java -jar一跑提示No main manifest attribute终于跑起来了却发现连不上数据库配置文件改了也没生效。这些问题我基本都踩过一遍也顺着源码和构建日志把它们琢磨清楚了。这篇内容准备把Spring Boot打包这条链路完整讲透既讲可执行Jar的原理也讲Maven命令和IDEA的实操、配置文件外置、Docker镜像打包、常见报错排查最后聊一下GraalVM原生镜像和Spring Boot 3.5虚拟线程这类进阶话题。无论你是正在做毕设或企业项目、需要把系统交给别人的学生还是刚接手部署的Java开发应该都能找到对应的答案。1. 先搞懂Spring Boot的可执行Jar结构很多报错会迎刃而解1.1 Fat Jar和传统Jar的差别Spring Boot默认打出来的包准确说叫可执行Jar也被叫Fat Jar或Uber Jar它和传统Java项目打出来的Jar有本质区别。传统Jar只包含自己编译好的class文件和资源文件所有第三方依赖都要额外放到一个lib目录里部署时要手工维护classpath依赖稍微多几个就容易漏。Spring Boot的可执行Jar则是“全家桶”把项目自身代码、所有第三方依赖、内嵌的Tomcat或Jetty服务器全部塞进一个Jar文件里。打开一个Spring Boot可执行Jar你会看到这样的目录结构demo.jar ├── BOOT-INF │ ├── classes # 项目编译后的class和resources下的配置文件 │ └── lib # 所有第三方依赖jar ├── META-INF │ ├── MANIFEST.MF # 启动入口信息 │ └── maven └── org └── springframework └── boot └── loader # Spring Boot自定义的类加载器这个结构很关键。BOOT-INF/lib里躺着所有依赖BOOT-INF/classes里是项目自己的代码和内置的application.yml。而org/springframework/boot/loader是Spring Boot自定义的类加载器JDK 9之后默认的应用程序类加载器不支持直接从嵌套Jar里加载class所以Spring Boot需要用自己的JarLauncher启动。我经常用“精装房和毛坯房”来打比方传统Jar像毛坯房家具和家电依赖需要你自己搬进去搬家麻烦缺一样都不行Fat Jar是精装房拎包入住里面一切都配好。理解这一点你就知道为什么Spring Boot项目部署起来这么方便了。1.2 repackage到底干了什么连main方法都“换了位置”很多教程只让你在pom.xml里加一个插件说“加上这个就能打包了”但很少解释这个插件到底做了什么事。这东西叫spring-boot-maven-plugin它的核心功能之一叫repackage在package阶段执行。它做的事情实际上是这样的Maven先按普通方式把项目打成一个常规Jar放在target下。spring-boot-maven-plugin读取这个刚生成的原始Jar。插件把原始Jar的内容挪进BOOT-INF/classes把所有依赖挪进BOOT-INF/lib。插件在META-INF/MANIFEST.MF里写入新的Main-Classorg.springframework.boot.loader.JarLauncher。你项目里真正的main方法对应的类被写进Start-Class属性。原始的Jar会被改名为*.jar.original保留在target目录。所以当你运行java -jar demo.jar时真正被JVM启动的是JarLauncher它先把嵌套在BOOT-INF/lib里的依赖全部加载进自定义类加载器然后再反射调用你写在Start-Class里的那个main方法。知道这个机制有什么用排查问题特别有用。比如你发现target目录下没有.jar.original文件说明repackage根本没有执行比如你运行Jar提示找不到main方法大概率就是Start-Class没配对或没生成。这些东西后面排错章节还会用到。1.3 No main manifest attribute是怎么来的“No main manifest attribute”应该是Spring Boot打包新手最常撞见的错误之一运行java -jar时直接报错退出。它的含义是Jar包的META-INF/MANIFEST.MF里没有Main-Class属性JVM根本不知道该从哪个类进去。出现这个问题的常见原因有三类项目没有继承spring-boot-starter-parent而是自己自定义了父POM导致spring-boot-maven-plugin没有被Maven的插件版本管理统一控制或者根本没有在build里配置这个插件。插件配置了但mainClass写错了路径repackage时找不到带main方法的类。打包命令里显式跳过了repackage执行。对应的解决办法也直接parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.5.3/version relativePath/ /parent然后保证build里有这个插件build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build如果父POM不方便改就在插件配置里显式指定入口类plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration mainClasscom.example.DemoApplication/mainClass /configuration /plugin排掉这个问题通常java -jar就能正常把项目拉起来。2. 标准打包流程与部署时的外部化配置2.1 命令行打包最稳妥IDEA打包只适合日常自测打包这件事我强烈建议你优先用命令行而不是IDE里的按钮。不是IDE按钮不能用而是命令行更可控、更容易复现也方便在CI/CD流水线里复用。最常用的一条命令是mvn clean package -DskipTestsclean会把target目录删掉避免上次构建的残留文件干扰package会执行编译、测试这一步被跳过、打包的完整链路。-DskipTests表示跳过测试代码的执行但测试代码仍然会编译。这里要区分两个经常被混用的参数它们在很多时候坑过人参数是否编译测试代码是否执行测试适用场景-DskipTests是否测试代码能编译通过只想跳过测试执行-Dmaven.test.skiptrue否否测试代码本身编译不过或者完全不想碰测试实际项目中如果团队没有跑完整测试的要求我一般直接用mvn clean package -Dmaven.test.skiptrue -Dcheckstyle.skiptrue第二个参数视项目里是否配置了代码检查插件而定跳过检查能省不少时间但只建议在本地打包或者确定代码没问题时这么干有CI流水线的项目还是把检查留给流水线。打包完成后target目录下会出现两个文件demo.jar和demo.jar.original。如果你用的是IDEA的Maven工具面板点package执行的其实是同一套逻辑但IDEA可能会用自己的Maven版本和JDK版本偶尔出现“IDEA能打包、命令行报错”或者相反的情况。所以遇到异常先用命令行跑一遍往往能暴露真实问题。2.2 配置文件外置config目录和命令行参数的优先级打包部署和本地开发最大的一个区别是本地配置直接改src/main/resources/application.yml就行但服务器上是不能随便改Jar包内部文件的。Spring Boot为此提供了一整套外部化配置机制优先级大致是这样的优先级高到低配置来源1命令行参数如--server.port80812Java系统属性如-Dserver.port80813操作系统环境变量如SERVER_PORT80814外部配置文件jar包同目录或同目录config/子目录的application.yml5Jar包内部的application.yml实际部署时我推荐的外部目录结构是这样/opt/app/ ├── demo.jar ├── config/ │ └── application-prod.yml └── logs/Jar包放在/opt/app/配置放在config子目录里Spring Boot启动时会自动读取config/application.yml或config/application-prod.yml并且优先于Jar包内部的同名配置。这样改端口、改数据库地址、改日志级别都只需要改服务器上的文件完全不用重新打包。还有一种更灵活的做法是启动时显式指定配置位置java -jar demo.jar --spring.config.locationfile:/etc/app/application.yml但注意使用--spring.config.location时默认的配置路径会被替代掉Jar包内部的配置就不会再被读取了。如果想保留默认配置只追加外部目录用--spring.config.additional-location更稳妥。关于数据库密码、Redis密码、MinIO密钥这类敏感信息我的经验是不要写进任何application.yml包括外置的配置文件。直接在启动命令里用--spring.datasource.passwordxxx传或者通过环境变量注入配合部署平台的密钥管理功能安全性和可维护性都会好很多。现在接入MinIO、Redis、MySQL这类中间件基本都走这套思路连接地址和凭据全部外置代码里只留占位符。2.3 MyBatis-Plus的XML与Mapper同包打包后查不到SQL这是搜索热词里非常典型的一个问题“Spring Boot项目使用MyBatis-PlusXML与Mapper在同一个文件夹下应该如何配置”。开发环境跑得好好的一打包部署就报Invalid bound statement (not found)。问题的根源在于Maven的资源过滤规则。默认情况下Maven把src/main/resources目录下的内容复制到target/classes而src/main/java目录只负责编译Java类里面的XML文件默认不会被当作资源复制过去。开发环境里IDEA会做一些额外处理所以你能正常读到XML但用mvn package打成Jar后BOOT-INF/classes里根本没有对应的XML文件运行时MyBatis自然找不到SQL语句。解决方案有两种。第一种是改pom.xml把src/main/java下的XML也纳入资源build resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource resource directorysrc/main/resources/directory /resource /resources /build这样XML会被复制到classes目录同时保留原包路径MyBatis就能按Mapper接口的包路径找到XML了。第二种方案是我更推荐的不要让XML和Mapper同包把XML统一放到src/main/resources/mapper/目录在配置里声明扫描路径mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml type-aliases-package: com.example.demo.entity这种做法的好处是目录职责清楚打包后XML天然在BOOT-INF/classes/mapper/下不会出幺蛾子多人协作时也更容易形成统一约定。顺带提醒一句如果你在pom.xml里对resources做了include过滤必须把所有需要打包的资源类型都写进去比如**/*.yml、**/*.properties、**/*.xml否则很容易出现“数据库配置丢失”“日志配置没生效”这种隐蔽问题。3. 打包出的Jar怎么跑运行参数、端口日志与常见“假死”3.1 一条标准启动命令和常用的JVM参数打包完成后最直接的启动方式是这样java -jar /opt/app/demo.jar --spring.profiles.activeprod --server.port8080--spring.profiles.activeprod用来激活生产环境的profile--server.port8080用来覆盖默认端口这些都属于优先级最高的命令行参数。内存参数方面本地测试可以这么写java -Xms512m -Xmx1024m -jar demo.jar-Xms是JVM启动时分配的初始堆内存-Xmx是最大堆内存。但真实部署的时候我强烈不建议在java -jar命令里写死-Xmx尤其是容器化部署时固定堆上限可能导致JVM能感知到的容器内存和-Xmx不一致出现OOM被系统杀掉或者明明还有内存却不用的尴尬情况。容器环境里更推荐用百分比参数这个放到Docker章节详细说。生产环境如果不想用nohup可以写一个简单的systemd服务单元文件[Unit] Descriptiondemo-app Afternetwork.target [Service] Typesimple Userapp WorkingDirectory/opt/app ExecStart/usr/bin/java -Xms512m -Xmx1024m -jar /opt/app/demo.jar --spring.profiles.activeprod SuccessExitStatus143 Restarton-failure RestartSec10 [Install] WantedBymulti-user.target这里SuccessExitStatus143是因为systemd停止Java服务时JVM会收到SIGTERM信号返回143要标注为正常退出码否则systemd会认为服务是异常停止的。Restarton-failure配合RestartSec10服务挂了能自动拉起。3.2 启动后不显示端口号不代表启动失败热词里有一条“IDEA启动Spring Boot项目不显示端口号”这个现象在真实部署中也很常见很多人看到控制台没有那句Tomcat started on port 8080就慌了以为启动失败结果Log里明明写着“Started DemoApplication in 3.2 seconds”。不显示端口号的原因通常是这几个spring.main.banner-mode被设置成off有些日志组件还会过滤掉Spring Boot启动时打印的信息导致你只看到部分日志。项目里配置了自定义的logback-spring.xml日志级别把org.apache.catalina、org.springframework.boot.web.embedded.tomcat这些类的启动日志级别调高了比如WARNTomcat的端口信息就不会出现在输出里。端口被占用Tomcat启动失败但错误堆栈被日志配置吞了一部分误以为“没显示端口”就是没启动。我的排查方法是先别管控制台输出直接看端口监听状态ss -lntp | grep 8080 curl http://localhost:8080/actuator/health如果端口在监听curl能通那服务就是起来了只是日志没打印而已。反过来如果端口没监听再去查完整日志文件里有没有Port 8080 was already in use或者Web server failed to start这种关键行。这儿再补一句如果你确认代码没问题、端口也没被占就优先检查logging.level配置把org.springframework.boot设为INFO端口信息会正常打出来。3.3 日志落盘和Spring Boot 3.5虚拟线程开关部署到服务器上的Jar不能只在控制台看日志必须落盘。项目里加一份logback-spring.xml按天滚动、保留历史日志、按大小切割这是基本操作。我常用的配置核心部分是这样的appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender file${LOG_PATH:-logs}/app.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePattern${LOG_PATH:-logs}/app.%d{yyyy-MM-dd}.%i.log/fileNamePattern maxHistory30/maxHistory timeBasedFileNamingAndTriggeringPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedFNATP maxFileSize100MB/maxFileSize /timeBasedFileNamingAndTriggeringPolicy /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n/pattern /encoder /appender需要滚动日志的时候启动命令里加上--logging.file.name/opt/app/logs/app.log或者用环境变量LOGGING_FILE_NAME指定Spring Boot会优先使用外置的日志路径配置这个做法对排查线上问题很有帮助。还有一个热词是“Java 21 Spring Boot 3.5启用虚拟线程”这个和打包运行关系也很紧密。Spring Boot 3.5已经完整支持虚拟线程启用方式就是在application.yml里加一行spring: threads: virtual: enabled: true虚拟机线程对高并发IO密集型应用有很明显的改善但有个前提项目必须运行在JDK 21及以上版本。如果你的服务器还是JDK 8或11编译出的Jar可能都跑不起来Spring Boot 3.x。所以打包前先确认编译JDK和运行JDK的版本一致性这个坑我见过不少次本地用JDK 17编译打包服务器上只有JDK 8启动直接报UnsupportedClassVersionError。4. 从Jar到Docker镜像部署环境的主流打包方案4.1 一个够用的Dockerfile和基础镜像选型现在多数项目的最终部署形态已经不只是Jar包了而是镜像。Spring Boot项目打成Docker镜像最简单的Dockerfile长这样FROM eclipse-temurin:17-jre WORKDIR /app COPY target/demo.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]有人会问为什么用eclipse-temurin而不是其他镜像。eclipse-temurin是Eclipse Adoptium项目维护的开源JDK发行版有官方更新安全修复及时而且在Docker Hub上体积控制得不错。选镜像时一般有这几个选择基础镜像大概体积适用场景eclipse-temurin:17-jdk400MB需要在容器里编译或调试eclipse-temurin:17-jre200MB左右大多数生产环境只运行不编译eclipse-temurin:17-alpine更小对体积敏感的场景但部分原生依赖可能需要额外处理我个人的习惯是常规Web服务用eclipse-temurin:17-jre就够了不要为了追求小体积盲目上alpine否则碰到需要编译原生代码的依赖会很折腾。如果你用的是Spring Boot 3.x注意基础镜像的JDK版本不能低于17。4.2 利用layers做镜像分层构建速度能差好几倍一个容易被忽略的优化是Spring Boot的镜像分层构建。Spring Boot 2.3之后spring-boot-maven-plugin支持把可执行Jar拆分成多个layer典型的分法是dependencies、spring-boot-loader、snapshot-dependencies、application这几层。在pom.xml里开启plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration layers enabledtrue/enabled /layers /configuration /plugin然后在Dockerfile里这样构建FROM eclipse-temurin:17-jre AS builder WORKDIR /builder COPY target/demo.jar app.jar RUN java -Djarmodelayertools -jar app.jar extract FROM eclipse-temurin:17-jre WORKDIR /app COPY --frombuilder /builder/dependencies/ ./ COPY --frombuilder /builder/spring-boot-loader/ ./ COPY --frombuilder /builder/snapshot-dependencies/ ./ COPY --frombuilder /builder/application/ ./ ENTRYPOINT [java, org.springframework.boot.loader.launch.JarLauncher]这么做的核心收益是Docker缓存。如果只复制依赖层依赖没有变化时Docker构建可以命中缓存mvn package之后重新构建镜像通常只需要重建最后那个application层构建时间从几十秒降到几秒。对比一下构建方式依赖变化时只改项目代码时非分层COPY整个Jar全量重建全量重建分层分COPY多个层依赖层重建只重建application层秒级完成很多团队CI/CD跑的慢一大半原因就是没用上这个特性。4.3 健康检查与容器内存参数Dockerfile里加健康检查对编排平台K8s、Docker Compose等特别重要。前提是要引入spring-boot-starter-actuator依赖暴露/actuator/healthHEALTHCHECK --interval30s --timeout3s --retries3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1容器里跑Java内存参数我建议这样写ENTRYPOINT [java, -XX:MaxRAMPercentage75.0, -XX:InitialRAMPercentage50.0, -jar, app.jar]为什么不用-Xmx因为容器限制的是整个进程的内存如果JVM的-Xmx写死了它不会根据容器的限制自动调整很容易出现OOM Kill。-XX:MaxRAMPercentage75.0的意思是JVM使用容器可用内存的75%作为堆上限留一部分给元空间、线程栈和本地内存。这是容器化Java应用最稳妥的做法之一。5. IDEA与Maven打包报错的完整排查链路5.1 代码里一切正常mvn package却报“找不到符号”这类问题在“IntelliJ Maven项目打包报错”的搜索里出现频率特别高表现通常是在IDEA里运行项目没问题但命令行mvn package时报一堆找不到符号、程序包xxx不存在。先说排查链路不要一上来就怀疑代码先执行mvn clean清掉target目录里的旧编译产物。IDEA的增量编译和Maven的全量编译机制不同旧class文件可能导致各种灵异错误。确认命令行用的JDK和IDEA里面的JDK是不是同一个版本。命令行执行java -version和mvn -version看java.version字段。IDEA里看File - Project Structure - Project的SDK设置。Spring Boot 3.x需要JDK 17以上如果Maven用的是JDK 11必然报“找不到符号”或者编译版本错误。检查项目的Maven配置。比如settings.xml里配置的JDK版本或者POM里的maven.compiler.source/target。看是不是多模块项目。如果是多模块公共模块没有先mvn install到本地仓库子模块编译时找不到公共模块的类也会报“程序包xxx不存在”。先对公共模块执行mvn install -Dmaven.test.skiptrue再执行整体打包。还有一个高发原因是Lombok。Lombok的注解处理器依赖JDK内部APIJDK版本过新、Lombok版本过旧时会直接导致编译阶段“找不到符号”或者“程序包lombok不存在”。我遇到过最典型的就是JDK 21配老版本的Lombok解决办法是把Lombok升级到支持JDK 21的版本1.18.30或者在POM的annotationProcessorPaths里显式声明Lombok版本。排查思路一句话总结先看编译环境再看依赖可见性最后才是代码本身。5.2 package阶段repackage失败Unable to find main class当你看到这样的报错时问题基本不在编译而在spring-boot-maven-plugin的repackage阶段[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:3.5.3:repackage [ERROR] Unable to find main class这个报错说明插件已经执行了但它找不到带public static void main(String[] args)方法的类。常见原因和应对办法项目里确实没有入口类或者入口类的main方法签名写错了检查一下方法签名是不是标准的public static void main(String[] args)。项目是多模块结构你给一个纯工具模块也挂了Spring Boot插件但它没有主类。工具模块要么不挂这个插件要么在插件的configuration里指定一个不存在的类名之前先确认它的实际用途。入口类被放在某个未参与构建的源码目录里比如放在了src/test/java而不是src/main/java。这个错误很低级但确实不少人犯过。我自己在多模块项目里的做法是只在真正需要生成可执行Jar的模块上配置spring-boot-maven-plugin其他模块只保留普通Maven编译插件。这样可以避免很多“找不到主类”的纠缠。5.3 本地仓库依赖损坏和镜像源问题还有一个经常让人抓狂的场景项目本身没问题但mvn package一直提示某个依赖下载失败或者突然报了一个Could not resolve dependencies。这种情况十有八九是本地Maven仓库的依赖包损坏了尤其是在换过镜像源之后。Maven下载依赖时如果断网或者被中断会在本地仓库生成.lastUpdated后缀的标记文件。之后Maven看到这些标记文件会拒绝重新下载导致同一个依赖一直报错。排查步骤先强制执行一次更新mvn clean package -U-U会让Maven强制检查所有依赖的最新快照版本跳过本地的.lastUpdated缓存判断。如果还不行直接清理本地仓库里所有失败标记find ~/.m2/repository -name *.lastUpdated -delete检查settings.xml里的镜像配置。国内网络环境下把中央仓库换成阿里云镜像通常会快很多mirror idaliyunmaven/id mirrorOfcentral/mirrorOf namealiyun public repository/name urlhttps://maven.aliyun.com/repository/public/url /mirror这四个点排查下来绝大多数“依赖加载失败”都能解决。要注意的是不要动不动就删整个.m2/repository那会触发全量重新下载非常浪费时间。6. 进阶话题GraalVM原生镜像与Spring Boot 3.5虚拟线程6.1 打包插件真的可以换成GraalVM吗热词里有一条“Spring Boot打包插件可以换成GraalVM”这说明很多人在探索新技术时都会有这个疑问。先说结论可以但不是简单地把spring-boot-maven-plugin换成另一个插件而是用org.graalvm.buildtools:native-maven-plugin来配合构建原生可执行文件并且只支持Spring Boot 3.x及之后版本。GraalVM原生镜像的思路和传统JVM有很大不同。它会在构建期对你的程序做静态分析把代码、依赖、必要的JVM运行时全部编译成一个独立的原生可执行文件。这样启动时不需要JVM解释和执行字节码所以启动飞快、内存占用也低。我见过一个普通的Spring Boot Web项目Jar包启动2秒多GraalVM原生镜像启动不到0.1秒内存占用也降了差不多一半。但代价也很明显对比项传统可执行JarGraalVM原生镜像启动时间1~5秒50~200毫秒内存占用较高明显更低构建时间十几秒到几十秒几分钟甚至十几分钟反射/动态代理无需特别处理需要额外配置文件或注解Spring Boot版本要求任意3.x官方支持原生镜像对反射、动态代理、ServiceLoader这类机制处理比较吃力。比如很多框架内部用反射来创建Bean构建时GraalVM无法推断出这些类就需要手动提供反射配置文件。Spring Boot通过RegisterReflectionForBinding、hints机制做了大量适配但仍然不是所有第三方库都能直接跑通。我的个人建议是常规业务系统、管理后台、内部服务老老实实用可执行Jar加Docker镜像部署稳定性优先。原生镜像的适用场景是短暂的Serverless函数、边缘计算、命令行工具或者对启动速度和资源占用有明确要求的场景。不要为了“新”而上先确认收益能覆盖成本。6.2 Java 21与Spring Boot 3.5虚拟线程的配置与注意点虚拟线程是Java 19引入预览特性、Java 21正式落地的能力Spring Boot 3.2起就开始支持虚拟线程Spring Boot 3.5对它的支持更成熟了。启用方式非常简单spring: threads: virtual: enabled: true开了这个配置之后Tomcat接收请求的线程会变成虚拟线程而不是传统的平台线程。对于大量IO密集型场景比如频繁查询数据库、调用远程接口虚拟线程能明显提升吞吐量代码不用改配置一行搞定。但要注意几个打包和运行层面的问题必须在JDK 21及以上版本编译和运行。用JDK 17编译的class文件在JDK 21上虽然能跑但Spring Boot 3.5对虚拟线程的支持代码本身是用21的特性编译的所以整个构建链路的JDK版本一定别低于21。虚拟线程并不适合所有代码特别是那些通过ThreadLocal保存大量数据、或者依赖synchronized做锁竞争的逻辑需要评估是否有隐患。容器里的线程池、连接池仍然要关注。虚拟线程解决了“一个请求一个线程”的资源瓶颈但数据库连接池、Redis连接池这些物理资源不会自动变大如果连接池设计不合理照样会排队等连接。我在测试环境里实测过一个模拟大量阻塞IO场景的Spring Boot服务开启虚拟线程后吞吐量大概提升了三倍以上但业务响应时间没有明显下降。说明虚拟线程对高并发场景的帮助是真实的但它不是万能药更不是用来替代合理架构设计的。6.3 我最想强调的几个打包实战经验走到这里Spring Boot打包的主干流程和常见坑基本都过了一遍。最后分享几个我自己在实际项目里沉淀下来的习惯不算进阶教程但能给打算开始做部署的同学省不少时间。第一项目里始终保留spring-boot-maven-plugin并且养成打包后看一眼target目录的习惯。里面有.jar.original说明repackage确实是执行过的没有的话就回去检查插件配置。第二配置文件外置从第一天就做。哪怕只有一台服务器也应该把application.yml放到config目录或用环境变量覆盖而不是每次改配置都重新打包。环境变量注入数据库密码、MinIO密钥、Redis地址的方式能让你在多个环境之间切换时完全不用动代码。第三Docker部署一定要用分层构建。团队协作时构建速度直接影响研发效率分层缓存带来的收益是实打实的。如果发现镜像构建特别慢优先检查是不是没有用layers。第四遇到打包问题不要急着搜索“No main manifest attribute”之类的报错原文先看完整日志再顺着repackage这个关键词找到问题根源。绝大多数打包问题都出在插件的执行状态、JDK版本一致性、资源配置这三个维度上定位到具体维度解决方向就很清晰了。最后再补一个扩展思路如果你已经在用Spring Boot 3.5和Java 21可以尝试做一个最小的演示项目跑虚拟线程再对比一下传统模式和原生镜像模式的区别。技术选型这种事亲自测得出来的数据比任何教程里的结论都靠谱。
返回列表