尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Spring Boot 3.5新特性实战:虚拟线程、Spring AI与自动装配

Spring Boot 3.5新特性实战:虚拟线程、Spring AI与自动装配 1. 这波新特性背后Spring Boot到底改了什么群里最近一直有人在问“SpringBoot版本太高不敢升怎么办”“3.x和2.x差别大不大”“Spring AI那个新项目到底怎么玩”说实话这些问题的背后其实是Spring Boot进入3.x时代以后的一系列底层变化。过去两年里Spring Boot从3.0一路走到3.5每个版本都在往“更省心、更贴近云原生、更拥抱AI”的方向推进。如果你还停留在2.x时代只改改配置、写写接口的思路那这篇新特性梳理值得好好看一遍。先给一个整体认知Spring Boot 3.x不是简单的版本号递增。它把JDK基线直接拉到17把Java EE迁移到Jakarta EE命名空间同时以Spring Framework 6为底座重构了大量模块。这意味着老项目升级不只是改个版本号的事而你新开项目时感受到的“轻、快、顺手”也不是错觉而是底层设计思路换了。本文我不打算讲空泛的概念而是围绕“新项目怎么建、老项目怎么迁、新特性怎么用、底层原理怎么理解”这四个方向把真正的干货拿出来讲。再说下适合谁看。如果你是准备入门的Java新人这篇帮你建立新版Spring Boot的整体认知如果你是在做老项目维护的工程师这篇能帮你理清升级路径如果你正在关注AI应用开发那Spring AI 2.0 M4这部分你绝对不能错过。哪怕是前端工程师接手一个Spring Boot项目看完这篇也能大概明白后端项目的结构和自动装配是怎么回事不会再对着HikariCP、Druid、RedisTemplate发愁。2. Spring Boot 3.5从“能用”到“更好用”的配置时代2.1 虚拟线程一行配置并发模型换了颗心脏Spring Boot 3.2开始支持JDK 21的虚拟线程到了3.5这个能力已经相当成熟。虚拟线程的价值不用我多吹一句话总结就是一个JVM进程能支撑的并发线程数从几千涨到几十万而且线程创建销毁的开销几乎为零。过去做高并发你得小心翼翼设计线程池控制队列长度防止OOM而现在如果项目里的IO密集操作占多数比如调数据库、调第三方接口、读写文件直接启用虚拟线程就行。启用方式很简单在application.yml里加一行配置spring: threads: virtual: enabled: true实测下来一个原本用固定线程池处理Web请求的Spring Boot 3服务切到虚拟线程后接口的吞吐量提升非常明显而且代码一行都不用动。当然这里有个容易踩的坑如果你在项目里用了ThreadLocal做上下文传递请务必确认你用的版本对虚拟线程的ThreadLocal做了正确处理。Spring Boot 3.5里Spring MVC和Spring WebFlux都增加了对虚拟线程的适配Tomcat和Jetty也都支持但像一些老的第三方库自带线程池、自定义ThreadFactory的场景建议先在压测环境验证。2.2 配置属性与Jackson绑定更顺手的开发体验Spring Boot 3.5在配置绑定上做了一个非常实在的改进基于Jackson的配置属性绑定。什么意思呢过去ConfigurationProperties绑定配置时使用的是Java Bean属性绑定要求属性有getter和setter而且对Map、List这类复杂结构的类型转换支持得不够优雅。现在Spring Boot支持把配置绑定逻辑交给Jackson来处理你在application.yml里的很多写法和JSON结构完全对齐而且可以使用record类型来写配置类。来一个最直观的对比老写法Component ConfigurationProperties(prefix app.demo) public class AppDemoProperties { private MapString, ListString rules new HashMap(); public MapString, ListString getRules() { return rules; } public void setRules(MapString, ListString rules) { this.rules rules; } }新写法ConfigurationProperties(prefix app.demo) public record AppDemoProperties(MapString, ListString rules) { }一个record类搞定没有样板代码配置文件的嵌套结构直接映射到嵌套Map。对于喜欢写函数式风格的人这个改进非常舒服。我个人在实际项目里会用record加Spring Boot 3.4以后新增的ConfigurationProperties自动注册特性通过ConfigurationPropertiesScan或者直接在启动类上标注配置类不再需要手动加Component整个项目结构清爽很多。2.3 可观测性升级与Docker Compose支持Spring Boot 3.x开始把可观测性提到了非常高的优先级。3.0引入了io.micrometer.tracing把各种分布式链路追踪实现比如Zipkin、OpenTelemetry统一抽象了一套API到了3.5对OTLP协议的支持已经非常成熟你在application.yml里配置一下就能把metrics和traces直接导出到OpenTelemetry Collector再对接Prometheus或者Grafana。这里我强烈建议新项目从第一行代码就接上可观测性等上了生产再补排查问题会非常痛苦。另一个很多人没注意但非常实用的特性是Docker Compose集成。最开始Spring Boot 3.1引入这个能力时大家只觉得“哦可以自动管理MySQL容器了”但3.5版本里它已经支持更多组件包括Redis、Kafka、MongoDB、Elasticsearch等。如果你本地开发想快速起一套依赖环境只需要在项目里放一个compose.yamlSpring Boot启动时会自动检测并启动对应容器。实测下来再也不用在本地手动敲docker run命令也不用担心容器端口被占用导致“Connection refused”。3. Spring AI 2.0 M4AI应用开发的“Spring式”体验3.1 ChatClient像写REST接口一样调用大模型Spring AI从2024年开始进入大家视野到Spring AI 2.0 M4这个里程碑版本本质上解决了一个问题把大模型接入从“自己拼HTTP请求、自己写JSON解析”变成“像操作Spring Data一样操作对话模型”。它提供了一个高层级的ChatClient接口风格上模仿WebClient调用流程非常直观。来看一个最简单的流式对话调用RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient builder.build(); } GetMapping(/chat) public String chat(String message) { return chatClient.prompt() .user(message) .call() .content(); } }如果你用过WebClient这个写法基本零上手成本。2.0 M4版本还完善了流式响应StreamResponseSpec、回调函数、系统提示词模板这些都封装好了你只需要关注业务逻辑不必关心底层模型API的细节。Spring AI还适配了多家模型提供商OpenAI、Ollama、Azure OpenAI、通义千问DashScope等都能接入改配置就能切换模型这对做项目选型的人来说是极大的便利。3.2 MCP与函数调用让模型去调你的业务方法Spring AI 2.0 M4里最值得关注的是对MCPModel Context Protocol模型上下文协议的支持。MCP的目标是让大模型能通过标准协议访问外部数据源和工具这个思路很像“AI世界的JDBC标准”——各家模型、各家企业只要遵守协议就能互通数据能力。在Spring AI里你可以非常优雅地把一个普通方法暴露给大模型作为工具调用Component public class OrderTools { Tool(description 根据订单号查询订单状态) public String getOrderStatus(String orderId) { // 实际业务逻辑 return orderService.getStatus(orderId); } }然后在对话中这样使用ChatClient chatClient ChatClient.builder(chatModel) .defaultTools(new OrderTools()) .build();当用户问“帮我查一下订单20250101的状态”模型会判断需要调用getOrderStatus工具自动解析参数并返回结果。实测下来最关键的是Tool注解里的描述要写清楚中文描述也要写得够细不然模型会傻傻分不清该用哪个工具。3.3 实际踩坑模型兼容与评估用Spring AI接入大模型我也踩过几个不小的坑。第一不同厂商的模型对Function Calling的格式支持差异很大Spring AI做了适配层但如果你用了比较冷门的私有化模型建议先用原生SDK验证一遍再套Spring AI。第二在2.0 M4里ChatModel的Bean注入方式每个版本有过调整如果你参考的博客是基于1.0 RC版本写的代码大概率跑不通。我的建议是直接看官方文档的“Getting Started”章节别自己瞎猜。另外Spring AI还提供了模型输出评估工具。简单说你可以定义一组评估标准和测试用例用断言判断模型回答是否满足需求。做AI应用最容易忽略的就是“版本回归”模型是概率输出你可能这周调好的Prompt下周换了模型版本又失效了。接入自动评估能提前发现问题这在生产环境里非常重要。4. 自动装配与配置属性吃透Spring Boot的底层逻辑4.1 自动装配原理拆解三类条件注解聊完新特性再回到很多面试都会问也是理解Spring Boot一切机制的基石自动装配。你会发现所谓新特性本质上都是自动装配机制在不同场景下的延伸。Spring Boot的核心是SpringBootApplication这个注解把EnableAutoConfiguration、ComponentScan、SpringBootConfiguration打包在了一起。EnableAutoConfiguration会加载META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里面列了一堆自动配置类。但自动配置类不是全都要生效它们会被条件注解过滤。条件注解主要分三类第一ConditionalOnClass判断某个类是否在classpath里比如引入spring-boot-starter-data-redis后RedisTemplate类存在RedisAutoConfiguration才会生效第二ConditionalOnMissingBean判断容器里有没有某个Bean如果没有就主动给你配置一个默认的最典型的就是ObjectMapper、RestTemplate第三ConditionalOnProperty根据配置文件里的某个属性值来决定是否生效。理解了这套机制你遇到“为什么我加了Redis依赖Redis配置没生效”“为什么我自定义了ObjectMapperJackson的自动配置不生效”这类问题就能快速定位。你在application.yml里看到那些spring.redis.*、spring.datasource.*配置本质上就是自动配置类读取配置属性然后组装出对应的Bean。4.2 ConfigurationProperties的最佳实践ConfigurationProperties这个注解在新版Spring Boot里的地位越来越高。它的作用是把你application.yml中一组以prefix开头的配置项绑定到一个Java类上。比如你在配置文件里写app: oss: endpoint: https://oss.example.com access-key: xxx secret-key: yyy bucket: my-bucket然后建一个类ConfigurationProperties(prefix app.oss) public class OssProperties { private String endpoint; private String accessKey; private String secretKey; private String bucket; // getter和setter }再配合3.5的record新写法配置类可以非常简洁。这里要强调三个容易踩的坑。第一个坑Spring Boot 3.x里ConfigurationProperties不再建议使用Component方式注册推荐在启动类上加上ConfigurationPropertiesScan或者配置类用EnableConfigurationProperties(OssProperties.class)显式指定。如果你用Component方式很多情况下也能跑但会在IDE里看到弃用警告而且不便于模块化。第二个坑配置属性类如果在构造函数里做校验逻辑请一定注意属性绑定的时序。推荐使用Spring官方的校验注解比如NotBlank、Min放在record组件上Spring Boot会帮你做参数校验启动时就能发现配置错误。第三个坑多环境配置。很多人喜欢把不同环境的配置写在application-dev.yml、application-prod.yml然后在application.yml里用spring.profiles.active切换。这个思路没问题但要注意占位符处理比如两个环境都有app.oss.endpoint但prod环境想用环境变量注入可以在application-prod.yml里写endpoint: ${OSS_ENDPOINT}这样就能避免把敏感信息硬编码提交到代码库。4.3 自定义Starter把公司公共能力沉淀下来理解自动装配的后劲在于你能自己写Starter。我在公司里经常做的一件事把日志链路、公共配置中心、统一异常处理这些公共逻辑做成Starter新项目一行依赖就完事。自定义Starter的核心步骤。第一步建立一个普通Maven模块spring-boot-autoconfigure是必选依赖。第二步写自动配置类用条件注解控制生效范围。第三步在src/main/resources/META-INF目录下创建org.springframework.boot.autoconfigure.AutoConfiguration.imports文件每行列一个自动配置类全限定名。第四步如果需要读取自定义配置建一个ConfigurationProperties类并注册。贴一个最简单的自动配置类示例AutoConfiguration ConditionalOnClass(MyService.class) EnableConfigurationProperties(MyProperties.class) public class MyServiceAutoConfiguration { Bean ConditionalOnMissingBean public MyService myService(MyProperties properties) { return new MyService(properties); } }注意AutoConfiguration注解是新版推荐写法替代了老版Configuration。实测中还有一个常见细节自动配置类的生效顺序。如果多个Starter之间有依赖关系需要用AutoConfigureBefore、AutoConfigureAfter或者AutoConfigureOrder来控制顺序比如你的组件依赖RedisTemplate就要用AutoConfigureAfter(RedisAutoConfiguration.class)否则启动时就会因为Bean不存在而报错。5. 实操用最新版Spring Boot快速搭一个可用项目5.1 用Spring CLI三分钟建项目现在建Spring Boot项目我强烈推荐从Spring CLI或者start.spring.io开始别再从零手写pom.xml了。Spring CLI在3.x之后提供了init命令几秒钟就能生成一个可运行的项目骨架。spring init --buildmaven --java-version21 \ --dependenciesweb,data-redis,validation \ --group-idcom.example --artifact-idmyapp \ --namemyapp --package-namecom.example.myapp \ --typemaven-project --languagejava myapp.zip参数说明--dependencies可以填多个依赖用逗号分隔--java-version可以用17或者21--type可以选择maven-project或者gradle-project。生成后解压用IDEA打开直接就能跑。如果你用IDEA新建项目也可以直接选Spring Initializr可视化勾选依赖效果一样。这里我建议在刚开始学Spring Boot的阶段就养成“选择依赖版本”的好习惯在start.spring.io右下角可以选择Spring Boot版本默认通常是最新稳定版你就选最新即可不用怕版本高。5.2 老项目从2.x升级到3.x迁移步骤到了实际业务里“SpringBoot版本太高”通常是老项目升级时最头疼的事。我接手的几个项目就是从Spring Boot 2.7升到3.2踩了整整一周的坑。这里把最有价值的一套迁移流程分享一下你照着走能少走弯路。第一步先确定JDK版本。Spring Boot 3.x强制要求JDK 17及以上所以先在开发机装好JDK 17或21IDEA的Project Structure里把SDK切到17以上pom.xml里也同步修改properties java.version21/java.version maven.compiler.source21/maven.compiler.source maven.compiler.target21/maven.compiler.target /properties第二步替换Jakarta命名空间。把所有javax.servlet、javax.persistence等依赖改成jakarta.servlet、jakarta.persistence。这一步主要影响Web容器、JPA相关的代码常见的如HttpServletRequest、HttpServletResponse的import都要改。第三步升级第三方依赖版本。老项目里经常用到Druid、MyBatis-Plus、Swagger等组件这些组件对Spring Boot 3的支持比较晚。比如MyBatis-Plus要用mybatis-plus-spring-boot3-starter这个新坐标Swagger推荐用OpenAPI 3实现的springdoc-openapi。记住一个原则先查第三方组件官方文档确认是否有Boot 3兼容版本再动手改。第四步处理配置变化。spring.redis.改成了spring.data.redis.spring.datasource.xxx连接池参数基本没变但有些自动配置类的包名变了。如果你在pom里显式引入了spring-boot-starter-tomcat的某些排除配置升级后也要重新对照新版本格式。第五步全面跑测试。自动装配细节变化太多光靠本地启动看不出问题建议用spring-boot-starter-test写一轮冒烟测试然后把定时任务、消息队列、数据库访问这些核心链路在测试环境完整过一遍。实测中最容易遗漏的是定时任务的线程池配置在新版本里默认行为变了如果你依赖了旧的执行器参数建议显式声明一个ThreadPoolTaskScheduler来定制线程数。5.3 集成Redis与MyBatis-Plus的兼容处理新项目最常用的两个依赖就是Redis和MyBatis-Plus这里专门说下新版本里比较容易踩的坑。先看Redis。Spring Boot 3.x里Redis依赖只要添加spring-boot-starter-data-redis自动配置就会生效。注意配置文件前缀是spring.data.redis不是spring.redisspring: data: redis: host: localhost port: 6379 timeout: 3s lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0代码里注入RedisTemplate即可Service public class CacheService { private final RedisTemplateString, Object redisTemplate; public CacheService(RedisTemplateString, Object redisTemplate) { this.redisTemplate redisTemplate; } public void set(String key, Object value) { redisTemplate.opsForValue().set(key, value, 10, TimeUnit.MINUTES); } }这里有个坑Spring Boot默认的RedisTemplate用的是JdkSerializationRedisSerializer存进Redis的数据是二进制序列化格式肉眼没法看。如果公司规范和排查问题需要可读性建议自定义一个RedisTemplate默认使用StringRedisSerializer做key序列化、Jackson做value序列化。这属于老生常谈但每过几个版本就有人踩一遍。MyBatis-Plus这边新版要用sharding组维护的starterdependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency配合dynamic-datasource-spring-boot3-starter可以配置多数据源。我自己在项目里同时连了SQLServer和MySQL核心配置如下spring: datasource: dynamic: primary: mysql datasource: mysql: url: jdbc:mysql://localhost:3306/app?useSSLfalse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver sqlserver: url: jdbc:sqlserver://localhost:1433;databaseNameapp username: sa password: 123456 driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver然后代码里通过DS(sqlserver)切换数据源。这里提醒一下如果你的数据库连接池有连接延迟一定要把连接池参数里的connection-test-query配好MySQL用SELECT 1SQLServer用SELECT 1不然可能出现“连接虽然建立了但第一次查询超时”的诡异问题。6. 常见问题与排查技巧实录6.1 “SpringBoot版本太高”引发的系列问题和解法“版本太高”这个问题在不同场景下含义完全不同。有一种是开发环境JDK版本太低比如你还在用JDK 8想跑Spring Boot 3.5那启动根本起不来报错UnsupportedClassVersionError。一个解决方案是更新JDK但如果公司环境确实被锁死在8那只能退回到Spring Boot 2.7.x。另一种是依赖不兼容比如Spring Boot 3的starter引入了Spring Framework 6你项目里WebSocket用的旧库会编译报错或者运行期报NoClassDefFoundError。还有一种很常见的是“IDE里没有Spring Boot 3.x选项”这种情况通常是IDE版本太老确认与new project时的Spring Initializr服务匹配的问题建议更新IDE或者直接命令行建项目再导入。我个人的建议是如果是全新项目无脑上3.5 JDK 21如果是老项目迁移优先评估依赖兼容性把第三方依赖全列成一张表逐一确认。别相信“升完版本跑通启动就行”定时任务、MQ消费、分布式锁这些异步场景的兼容问题往往藏得很深。6.2 配置不生效、循环依赖等现场问题配置不生效这个问题我见过太多同事查半天最后发现是配置属性类没有注册。用ConfigurationProperties(prefix app.oss)但启动类上没加ConfigurationPropertiesScan导致这个类和配置完全没绑定接口里注入的OssProperties是null。还有一个高频问题是自定义Bean和自动配置之间的冲突。当你自己写了一个RedisTemplate Bean时Spring Boot的自动配置大概率会失效因为ConditionalOnMissingBean判断容器里已经有了就不再创建默认Bean。这其实是特性不是BUG。但如果你写的Bean对配置的读取不完整就会导致部分属性没生效。排查思路很简单启动时加--debug参数。java -jar myapp.jar --debug启动日志里会打印ConditionEvaluationReport准确告诉你哪个自动配置类生效了、哪个因为什么条件没生效比靠猜快得多。生产环境排查时可以用Spring Boot Actuator的/actuator/conditions端点效果一样。循环依赖问题也是老项目升级后的常客。Spring Boot 2.6开始默认不允许循环依赖了老项目如果代码质量一般升级到3.x后大概率会遇到The dependencies of some of the beans in the application context form a cycle的报错。解决办法不是把allow-circular-references改成true压制问题而是建议重构掉循环依赖。最实用的重构方式是引入Lazy注解延迟依赖注入或者把强依赖关系用事件发布/监听机制解耦后者的代码质量会明显更好。6.3 新特性落地时的避坑清单这个清单是我在实际项目里沉淀下来的遇到对应场景可以对照自查。第一启用虚拟线程前先检查项目里有没有使用synchronized静默优化、有没有自定义线程池做阻塞操作、有没有用Object.wait/notify这种机制。虚拟线程挂起时释放的是载体线程逻辑上没问题但某些老库的Lock实现会有坑压测时才能暴露。第二用Spring AI时记得把模型的api-key放到环境变量而不是配置文件提交到Git。可以配合Spring的配置项占位符实现spring.ai.openai.api-key${OPENAI_API_KEY}。另外模型调用是外部IO一定要设置超时时间和调用次数限流不然一个接口调爆了你的模型账户余额。第三可观测性配置一定要留出日志与Trace ID的关联字段。Spring Boot 3.x的Trace Id默认通过MDC写入日志logback配置里加上%mdc{traceId}占位符排查跨服务问题会轻松很多。这个配置别等高并发时再补平时性能压测就会看出差距。第四Docker Compose支持虽然方便但生产环境别用。明确一下它是本地开发起依赖用的便利工具生产环境应该用成熟容器编排工具。你可以在compose.yaml里固定好镜像版本避免本地开发和生产镜像版本不一致。第五切换到Jackson绑定配置属性后注意配置值的格式。比如Duration属性过去用数字3000表示毫秒现在建议用3s这种ISO-8601字符串不然会遇到类型转换异常。类似的问题在DataSize、Period等类型上也会出现用新版Java 8类型系统体现配置语义能减少很多错误。结尾说点实际操作外的体会内容最后分享几个我自己在实战中的体会。第一Spring Boot的新特性固然诱人但生产环境不要盲目追求最新。我通常的做法是等小版本发布后过一两个月社区反馈稳定了再升级比如3.5.0刚出来时可以先在内部原型项目里试跑线上核心系统至少等到3.5.x的patch版本。第二阅读源码不一定要逐行读抓重点就行自动装配的AutoConfiguration.imports、条件注解的ConditionalOnClass、配置类的ConfigurationProperties这三个点搞明白Spring Boot的底层逻辑基本就通了。最后新项目和新技术最大的优势不是“新”本身而是让你的注意力放在业务逻辑而非框架配置上——这才是Spring Boot一直以来的核心目标。真遇到疑难问题配置文件加--debug、Actuator端点、StackOverflow上的真实案例比改配置试错高效得多。
返回列表