☰
Spring Boot依赖选型实战指南:从Starter到微服务组件的核心清单
2026/10/20 7:56:11 网站建设 项目流程

1. 项目概述

1.1 核心需求解析

Spring Boot 开发中,依赖选择直接影响项目的开发效率和运行稳定性。我见过太多刚接触 Spring Boot 的朋友,在创建项目时习惯性地往后端脚手架里堆一堆看似主流、实则并不必要的依赖,结果没等业务代码写完,光是处理依赖冲突、启动失败、Bean 注入报错就耗掉了大半天。这个标题要解决的,不是"能用的依赖有哪些",而是"哪些依赖值得被选入你的项目,以及为什么选它"。

我个人的理解是,Spring Boot 的依赖选型本质上是一次资源配置决策。你可以把项目想象成一辆赛车:Spring Boot 是底盘,starter 是标准配件,第三方库是改装件。底盘决定基础性能,配件决定功能边界,改装件决定你能跑多快的上限。选错了,要么车跑不起来,要么跑到一半散架。所以这篇文章我会从实际开发角度出发,把创建 Spring Boot 项目时最常用的依赖分成几个层次来讲:核心启动层、数据访问层、接口与安全层、微服务组件层、开发效率工具层,每一层都会结合我的实战经验说明它的用途、适用场景和选型理由。

1.2 适用的读者范围

这篇文章面向三类读者。第一类是刚入门 Spring Boot 的开发者,你不需要看过官方文档,只要会用 IDE 和 Maven 就能跟上,我会尽量把每个依赖讲得直白易懂;第二类是已经能跑通一个简单 CRUD 项目、但不知道该往项目里加入哪些依赖的初级开发者,这里会给你一份可以直接参考的依赖清单和理由;第三类是写过一段时间但从来没有系统梳理过依赖选型的开发者,这篇文章的价值在于帮你建立一套"选依赖的思考框架"——不再盲目复制别人的 pom.xml,而是能自己判断某个依赖该不该进项目。

在具体展开之前,先说一个核心观点:Spring Boot 的依赖管理能力其实已经被 Spring 官方封装得很成熟了,spring-boot-starter-parent 里的 dependencyManagement 已经锁定了大部分常用依赖的兼容版本,所以你在选依赖时的重点不在于"怎么配版本",而在于"选哪个 starter、它解决什么问题、引入后会带来哪些新的注意事项"。理解了这一点,后面的内容你会看得更顺畅。

2. 理解 Spring Boot 的 starter 机制

2.1 Starter 与传统依赖的本质区别

在 Spring Boot 出现之前,用 Spring 搭建一个 Web 项目要经历什么?拉一堆 jar 包——spring-core、spring-web、spring-webmvc、jackson-databind、tomcat-embed-core、servlet-api,然后写一堆 XML 配置,配置视图解析器、配置扫描包路径、配置数据源、配置事务管理器。这个过程的痛点是:依赖之间的版本兼容性完全靠人肉保证,配置与依赖的对应关系散落在不同的配置文件和文档里,换一个环境(比如从 Tomcat 切到 Jetty)要重写一堆配置。

Spring Boot 的 starter 机制把这个问题从根本上解决了。每一个 starter 本质上就是一个 Maven 聚合模块,它的 pom 文件里通过依赖传递把某个功能所需的一整套库全部引入进来,同时把版本号交给 spring-boot-dependencies 统一管理。更关键的是,starter 遵循约定优于配置的原则,引入 web 场景的 starter 后,它会自动装配内嵌的 Tomcat、Spring MVC、Jackson 等组件,你不需要写一行 XML 配置,应用就能以 main 方法启动。

我用一个生活化的类比来解释:传统方式就像你要搬家,冰箱、洗衣机、电视机、床垫你得分别联系不同的配送商,约不同的时间,还要自己确认尺寸能不能放进电梯;starter 方式就像一个打包好的搬家服务,你只需要告诉服务商"我要搬一居室",他会把该运的家电、家具、工具全部按合适的尺寸配齐,一辆车拉过来,你只管开门收货。

这里有一个新手容易忽略的点:starter 不是越多越好。starter 引入后会触发对应的自动装配逻辑,哪怕你从不使用某个功能,只要它在 classpath 里,某些自动配置类就会尝试加载。比如引入 spring-boot-starter-data-redis 后,Spring Boot 会尝试自动配置 RedisConnectionFactory,如果你的项目里没有任何 Redis 相关配置,启动时虽然不会报错(因为条件注解判断了缺失的类),但会白白增加启动扫描成本。我见过一个项目,明明只是写个简单的 CRUD 接口,pom.xml 里塞了 Redis、Elasticsearch、RabbitMQ、Kafka 四个 starter,启动时间从 3 秒拖到 15 秒,排查了半天才发现是多余依赖导致的自动装配开销。

2.2 为什么选择官方 starter 而不是普通依赖

还有一个很容易被忽略的问题:很多第三方库本身提供了 Spring Boot 官方 starter 之外的独立集成方式,那要不要用 Spring 官方的 starter?我的建议是优先用官方 starter,原因有三个:

第一,版本兼容性有保障。spring-boot-dependencies 中的版本号组合经过了官方大量测试,能减少因版本不一致导致的 NoSuchMethodError、ClassNotFoundException 等诡异问题。

第二,自动装配已经写好。官方 starter 通常会配套一个 spring.factories 或 AutoConfiguration.imports 文件,定义了该功能自动装配的入口类。你自己的项目不用写 @Configuration 直接动态注册组件,配置量小很多。

第三,文档和社区案例丰富。遇到问题搜解决方案时,官方 starter 的问法(比如 spring-boot-starter-data-redis)有大量现成的踩坑记录和经验总结,比自定义依赖组合的问题排查路径清晰得多。

有一种情况我建议换第三方 starter:官方 starter 功能过于基础,无法覆盖你的业务场景。典型例子是数据库操作方面,官方提供了 spring-boot-starter-data-jpa,但如果你偏爱 MyBatis 的动态 SQL 能力,那引入 mybatis-spring-boot-starter 是更合理的方案。这类第三方 starter 往往由厂商或社区维护,更新频率高、适配紧跟 Spring Boot 版本,用起来不比官方差。

3. 核心启动层:最常被低估的 starter

3.1 spring-boot-starter-web 与内嵌容器选择

我遇到不少人对 spring-boot-starter-web 的认知停留在"它让项目成为 Web 项目"这一层。实际上,这个 starter 做了很多事情:引入 Spring MVC 作为 Web 框架,引入内嵌的 Tomcat 作为默认容器,引入 Jackson 用于 JSON 序列化,引入 spring-boot-starter-tomcat 管理容器生命周期,还会顺带引入 spring-webmvc 的完整依赖栈和校验库的 JSR-303 支持。

这里有一个选型细节:默认的 Tomcat 并不总是最佳选择。如果你的应用对内存占用敏感(比如部署在只有 256M 内存的容器里),或者对启动速度有硬性要求,可以考虑排除默认容器、替换成 Undertow 或 Jetty。我在一个实际项目中就是通过把 Tomcat 替换为 Undertow,让应用的内存占用从 300M 降到了 220M 左右,启动时间也缩短了约 30%。替换方法很简单:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency>

不过要注意,Undertow 对 HTTP/2 的支持需要额外配置,Jetty 对 WebSocket 的兼容性在不同版本上有差异。如果不是有明确的性能诉求,新手不要一上来就换容器,默认的 Tomcat 足够覆盖绝大多数场景。

3.2 spring-boot-starter-parent 与 dependencyManagement 的角色

如果你用 IDE 或 start.spring.io 创建过 Spring Boot 项目,会发现生成的 pom.xml 最上面通常写着:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.x</version> <relativePath/> </parent>

这个 parent 的作用比很多人想象的更重要。它本身定义了 Java 编译版本、字符集编码、插件配置(比如 spring-boot-maven-plugin),更核心的是它继承了 spring-boot-dependencies,而后者通过 dependencyManagement 声明了大量常用依赖的版本号。也就是说,你在引入 spring-boot-starter-web 或者 jackson-databind 时,不需要写版本号,Maven 会按照 dependencyManagement 里锁定的版本解析。

这里有一个我反复踩过然后终于记住的坑:dependencyManagement 锁定的是"建议版本",如果你强行在 dependencies 里给某个库指定了一个完全不兼容的版本,编译可能不报错,但运行时大概率会出现各种奇怪问题。有一次我把 jackson-databind 的版本手动改成项目里某个旧依赖要求的 2.11.4(Spring Boot 3.x 默认要 2.15+),启动后 JSON 序列化某些类型时直接抛出了 InvalidDefinitionException。所以我的经验是:除非你对版本差异有十足的把握,否则不要手动覆盖 dependencyManagement 里的版本。

3.3 spring-boot-starter-test 与测试基础设施

测试是很多实际项目里最容易被敷衍的部分,但 spring-boot-starter-test 这个依赖我觉得每个项目都必须引入。它把 JUnit 5、Mockito、AssertJ、Hamcrest、Spring Test、JSONPath、JsonUnit 等一系列测试相关的库聚合在一起,你不需要分别引入,一个依赖搞定单元测试和集成测试的基础设施。

实战中我最常用的是 @WebMvcTest 和 @DataJpaTest 这两个切片测试注解。它们只加载 Web 层或数据访问层的必要组件,不会启动完整的 Spring 容器,测试速度非常快,适合在以 service 为核心的单元测试之外补充接口级别和数据访问级别的覆盖。需要注意的是,切片测试默认使用的是内存数据库,如果你的实体类依赖了非 H2 特性,需要额外配置 H2 的兼容模式。我一般在 pom 的 test 作用域里显式加上 H2 依赖:

<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>test</scope> </dependency>

另外提一句:spring-boot-starter-test 自带的 spring-boot-test-autoconfigure 支持很多实用的测试工具,比如 @MockBean 可以替换容器中的 Bean,@TestConfiguration 可以在测试里补充测试专用配置。这些工具比手动写 mock 代码省事得多,但我不建议在单元测试过度依赖 @MockBean,因为它每次都会重建部分容器上下文,测试速度会变慢,适合在集成测试和部分跨层测试里使用。

4. 数据访问层:连接业务与数据库的关键依赖

4.1 Spring Data JPA 与 Hibernate 的搭配细节

如果项目面向的是典型的业务管理系统,表结构相对稳定、关联关系清晰、不需要特别复杂的查询,我个人会优先推荐 spring-boot-starter-data-jpa。它内部引入了 Spring Data JPA 和 Hibernate,提供了基于方法名自动生成查询语句的能力,比如:

public interface UserRepository extends JpaRepository<User, Long> { List<User> findByNicknameContaining(String nickname); }

定义完这个方法,Spring Data 会自动生成类似select * from user where nickname like ?的查询,你不需要手写一句 SQL。

选 JPA 的核心原因在于:它能大幅减少数据访问层的样板代码,并且实体类与表结构的映射清晰。但这里有几个实践层面需要接受的挑战:N+1 查询问题是 JPA 新手最容易踩的坑。比如你查 100 个订单,每个订单关联一个用户,默认懒加载情况下,遍历订单时获取用户会产生 1+N 条 SQL。解决办法有三种:用 @EntityGraph 显式指定抓取策略、用 join fetch 写法、或者在 DTO 投影查询时直接用 JPQL 构造查询结果。我建议在集合查询场景默认使用 @EntityGraph:

@EntityGraph(attributePaths = {"user"}) @Query("select o from Order o where o.createTime between :start and :end") List<Order> findOrdersWithUser(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end);

还要注意 Hibernate 的方言问题。Spring Boot 会根据你配置的spring.jpa.database-platform自动选择方言,但如果你使用的数据库类型不在默认支持范围内,手动配置方言的字符串比较值版本敏感,写错就直接报错,排查起来比较烦。

4.2 MyBatis Plus 与 MyBatis 的选型对比

如果你讨厌 JPA 的抽象层次,已经习惯手写 SQL,或者项目里大量依赖复杂联表查询和存储过程,MyBatis 系才是更合理的选择。Spring Boot 整合官方 MyBatis 可以用 mybatis-spring-boot-starter(由 MyBatis 官方提供),它同样支持基于注解的 SQL 写法,也可以使用 XML Mapper。

我的实际经验是:如果从零开始一个新项目,且确定选 MyBatis,我一般会直接用 MyBatis Plus 这个增强库。它相当于在 MyBatis 之上封装了通用的 BaseMapper,大部分单表 CRUD 不用写 SQL,同时保留了 XML 动态 SQL 的灵活性。这个组合非常香,因为它既解决了 MyBatis 的"基本 CRUD 也要手写 SQL"问题,又避免了 JPA 在复杂查询上的别扭。

MyBatis Plus 引入如下:

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.x</version> </dependency>

使用上要注意三个点:分页插件必须单独配置,哪怕底层是 MyBatis Plus 自带的分页能力,也需要注册一个 MybatisPlusInterceptor 并添加 PaginationInnerInterceptor;逻辑删除字段要启用 @TableLogic 注解,否则删除操作走的是物理删除,跟逻辑删除设计就不一致了;字段自动填充(比如创建时间、更新时间)需要自定义 MetaObjectHandler 处理器,不要在业务代码里手动 set 时间字段,那样每张表都要写一遍,维护成本高。

4.3 Spring Data Redis 与缓存一致性

缓存是现在几乎逃不掉的需求,而 Spring Boot 项目里最常用的缓存方案是 Redis。引入依赖很简单:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

这个 starter 会自动装配一个 RedisTemplate 和一个 StringRedisTemplate。但默认的 RedisTemplate 用的是 JdkSerializationRedisSerializer,会产生两个问题:一是序列化后的内容不可读,二是跨语言调用(比如别的服务用 Python 读这个 key)时没法直接解析。所以我在实际项目中会配置 RedisTemplate 使用 StringRedisSerializer 做 key 的序列化,使用 Jackson 序列化做 value 的序列化,同时把 value 对应的类加上类型信息,这样其他系统的消费端拿到以后也能正确反序列化。

缓存上需要注意的一致性问题,我在排障时被问过太多次了:缓存更新不要只靠存缓存,还要考虑更新数据库后如何处理缓存。最简单的方案是加入 spring-boot-starter-cache,然后在方法上标注 @CacheEvict 和 @Cacheable,让数据库更新后主动清理缓存。不过这个方案在分布式环境下面临缓存击穿、雪崩等风险,对于更复杂的业务场景,建议在服务层手工管理缓存操作,不要过度依赖注解。

4.4 数据导入导出:EasyExcel 与 POI 的取舍

考虑导入导出场景的时候,我第一反应是 EasyExcel 而不是 POI。EasyExcel 是阿里开源的一个 Excel 处理工具,它对 POI 做了大量封装,大幅降低了内存占用。普通 POI 在读写几十万行大文件时,内存消耗能到几百兆甚至上G,EasyExcel 通过流式解析能把内存控制在可接受范围内。

项目里引入:

<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.x.x</version> </dependency>

实际使用时的经验是:EasyExcel 的注解模型非常方便,比如@ExcelProperty(value = "用户名", index = 0)可以把实体字段直接映射为 Excel 表头。但在大批量导入时,不要一条条入库,而应该拼装成批次,用 MyBatis 或 JPA 的批量插入方法,否则几万行数据逐条 insert,耗时能到分钟级。我在本地测试过一个 5 万行的导出再导入,逐条插入和批量插入的性能差距大概是 30 倍,量一上来差距非常夸张。

5. 接口、安全与校验:依赖层面的保障机制

5.1 spring-boot-starter-validation 的参数校验实践

一个接口最少要做的工作是参数校验。用 if else 在 controller 里写一堆判断,既冗长又容易漏。spring-boot-starter-validation 是基于 Jakarta Bean Validation 的标准实现,配合注解就能完成声明式校验:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>

用法是在 DTO 字段上标注解:

@Data public class UserCreateRequest { @NotBlank(message = "用户名不能为空") @Length(min = 2, max = 20, message = "用户名长度需在2-20位之间") private String username; @Email(message = "邮箱格式不正确") private String email; @Min(value = 1, message = "年龄最小为1") @Max(value = 150, message = "年龄最大为150") private Integer age; }

然后 Controller 的入参处加 @Valid 或 @Validated 注解。这里有一个实践中特别容易遇到的问题:校验失败默认返回 400 错误,但返回体里的字段信息是一串异常结构,前端解析很不方便。我建议在项目里加一个全局异常处理器,捕获 MethodArgumentNotValidException,提取出所有字段校验消息,组装成统一响应结构返回给前端。别觉得这是额外工作量,在前后端联调阶段,统一且友好的校验返回信息能少掉很多沟通成本。

5.2 Spring Security 与 JWT 的整合思路

涉及到登录认证的项目,spring-boot-starter-security 是绕不开的选择。它提供了完整的认证授权框架、密码加密工具(BCrypt)、会话防护和 CSRF 防护。引入:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>

不过在实际项目中,我很少直接用它默认的表单认证,基本都会配合 JWT 方案,把认证做成无状态接口。大体思路是:用户登录成功后生成一个签名后的 JWT token 返回给前端,后续请求在 Authorization header 中携带这个 token,一个自定义的 OncePerRequestFilter 负责解析 token、加载用户信息、填充 SecurityContext。

这里有一个新手容易犯的错误:Spring Security 默认开启 CSRF 防护,而无状态的 JWT 接口不需要 CSRF 防护,如果不显式关闭,POST 请求会莫名其妙地被 403 拦截。所以整合时需要在 SecurityFilterChain 配置里调用csrf().disable()(Spring Security 6 写法是csrf(AbstractHttpConfigurer::disable))。

另一个经验是:密码存储一定要用 BCryptPasswordEncoder,不要自己写 MD5 加盐。MD5+自定义盐的方案在彩虹表攻击和计算能力增强的背景下已经不够安全了。Spring Security 内置的 BCrypt 自动处理盐值,每次加密结果都不同,校验时用 matches 方法比对即可,这个成本很低但安全收益非常大。

5.3 API 文档自动生成:springdoc-openapi 的接替

以前大家习惯用 Springfox,但 Springfox 对 Spring Boot 3.x 和 OpenAPI 3 的支持已经不太跟得上了,我自己现在统一选 springdoc-openapi。它可以根据项目里的 Controller、实体类、注解自动生成 Swagger UI 文档,引入方式:

<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.x.x</version> </dependency>

Spring Boot 3 对应的是 springdoc-openapi-starter-webmvc-ui,Spring Boot 2 对应的是 springdoc-openapi-ui,别搞混。用它的时候,我建议给 Controller 上的 @Tag 和 @Operation 注解认真命名,这些内容会直接展示在文档界面上,团队的协作体验会好很多。同时在接口返回结构上尽量使用统一的 Response 包装类,这样生成的文档更规范,前端拿去也能少问不少问题。

6. API 通信与微服务:从单体到分布式的依赖配置

6.1 单体项目里的 HTTP 客户端选择

就算目前是单体项目,也难免要调用第三方系统提供的 REST API。Spring Boot 3 推荐的方式是 RestClient,它是对原有 RestTemplate 的补充,提供流式 API 和更清晰的错误处理。不过考虑到很多老系统还在用 RestTemplate,我这里先说一个经验:RestTemplate 默认没有连接池和超时限制,生产环境必须配置。我见过一个线上事故,调用第三方接口时对方迟迟不响应,RestTemplate 默认的超时设置几乎等于无限等待,线程全部卡在 IO 上,应用整体被拖垮。

我目前的项目里使用 WebClient(Spring WebFlux 里的客户端)或者 RestClient 比较多。RestClient 配置简单,适合阻塞式调用;WebClient 支持响应式流,适合需要并发调用多个外部接口、聚合结果的场景。如果只是最普通的调用,RestClient 就够了:

RestClient client = RestClient.builder() .baseUrl("https://api.example.com") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build();

别小看客户端的选型,它和连接池、超时、重试机制直接相关。如果用了 RestTemplate 又没做定制,强烈建议至少设置 connectTimeout、readTimeout,必要时引入 Apache HttpClient 或 OkHttp 作为底层实现。

6.2 微服务场景下的核心依赖组

准备拆分微服务时,Spring Cloud 相关依赖是核心。我自己最常用的是 spring-cloud-starter-openfeign(声明式 HTTP 客户端)、spring-cloud-starter-netflix-eureka-client(服务注册发现)、spring-cloud-starter-gateway(网关)。

引入这些依赖需要额外注意 Spring Cloud 版本号和 Spring Boot 版本号的对应关系。Spring Cloud 的版本名比较特殊,比如 2023.0.x 对应 Spring Boot 3.2.x。不需要一个一个查,直接引入 spring-cloud-dependencies 的 BOM 即可:

<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2023.0.x</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

还有一个比较新的选择是 spring-cloud-starter-consul-discovery,用 Consul 做注册中心和服务发现。相比 Eureka 更适合追求服务健康检查、多数据中心、KV 存储的场景。但学习和部署成本高一些,如果你刚接触微服务,我建议从 Eureka 或 Nacos 入手,先跑通链路再逐步换组件。

6.3 消息队列:RabbitMQ 还是 Kafka

项目里有异步解耦、削峰填谷或事件驱动需求时,消息队列就进入了视野。Spring Boot 集成 RabbitMQ 用的是 spring-boot-starter-amqp,集成 Kafka 用的是 spring-boot-starter-kafka。

选型的核心标准其实是你对吞吐量和消息可靠性的侧重点。RabbitMQ 提供了成熟的消息确认、延迟队列、死信队列机制,适合订单超时关闭、邮件通知、任务分发这类需要复杂路由和精确可靠投递的业务;Kafka 以高吞吐、分区有序性见长,适合日志采集、行为追踪、用户画像等大数据流场景。

如果你选了 RabbitMQ,我建议从一开始就设计好消息的确认模式。Spring Boot 默认开启了自动确认,但业务处理失败时消息可能已经确认成功,导致消息丢失。我一般配置为手动确认:在处理逻辑里 try-catch,成功则 basicAck,失败则 basicNack 并设置 requeue=false,配合死信队列做补偿。这个模式第一次配会有些绕,但一旦业务复杂起来,收益非常明显。

7. 开发效率工具:让写代码的过程更顺滑

7.1 Lombok 与 MapStruct 的组合用法

Lombok 几乎是 Java 项目绕不开的存在。它通过注解在编译期生成 getter/setter、构造函数、builder 方法等样板代码。引入方式:

<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>

为什么设 optional?因为这个包只在编译期使用,不需要打进最终的 jar 包里。在 IDE 中你需要安装 Lombok 插件,且要注意:如果项目升级了 JDK 版本,Lombok 版本也要同步升级,某些老版本在高版本 JDK 下会直接编译失败。

MapStruct 是另一个我很依赖的代码生成工具,它负责处理 DTO 和实体类之间的转换。以前这类对象拷贝要么手写 getter/setter,要么用 BeanUtils,但 BeanUtils 的反射性能差、字段名不一致时不报错。MapStruct 是在编译期生成类型安全、性能接近手写代码的映射实现,与 Lombok 配合时几乎无感。引入:

<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.x</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.x</version> <scope>provided</scope> </dependency>

有一个坑:MapStruct 和 Lombok 同时使用时,注解处理器的执行顺序必须正确,不然 Lombok 生成的 getter/setter 方法对 MapStruct 不可见,编译会报"无法获取属性"。解决办法是在 Maven 编译器插件上配置 annotationProcessorPaths,把两个处理器都列出来并按顺序执行,这个配置我每次建项目都会先写好:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin>

7.2 spring-boot-devtools 的适用边界

spring-boot-devtools 是一个开发期依赖,提供自动重启、LiveReload、远程调试等能力。引入后,改动类文件或配置文件会触发应用自动重启,不用每次手动重启应用,对日常开发效率提升很明显。

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <scope>runtime</scope> </dependency>

但有一条强制提醒:生产环境不要保留该依赖。即使 optional 设置为 true 也要保持它的 scope 为 runtime 且不会被打进生产包,因为 devtools 的类加载机制和自动重启特性在生产环境会引发各种诡异问题。打包前检查一下mvn package结果即可确认 jar 里是否包含相关 class。部署到生产环境后如果发现应用出现无规律的类加载冲突,先查 devtools。

我在实际项目里的策略是:devtools 只出现在本地的开发 profile 中,并且会在构建脚本里强制禁用 spring-boot.devtools.restart.enabled 配置项,确保 CI 和测试环境不会意外触发自动重启。

7.3 配置中心:spring-boot-configuration-processor 的隐藏价值

这个依赖很多人不知道,但对于要写自定义配置项的项目价值很大。它的作用是在编译期生成配置元数据文件,让 IDE 在 application.yml 里写配置时有自动补全和类型提示。引入方式:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>

我一般配合 @ConfigurationProperties 使用。假设你定义了一个AppProperties类:

@ConfigurationProperties(prefix = "app") @Data public class AppProperties { private String name; private Integer maxRetry; }

加上上面的 processor 依赖后,IDE 里输入app.时会自动提示name和maxRetry,还带类型校验。这个体验在没有该依赖时会变成:写错配置名根本不报错,运行时拿到 null 才去排查。所以别觉得这个依赖可有可无,它其实是配置管理的隐形守护者。

8. 依赖选型的避坑经验与版本策略

8.1 版本冲突与依赖排除的实战记录

Spring Boot 项目中大概 80% 的编译和启动异常都跟依赖版本或依赖冲突有关。具体表现有:启动时 NoSuchMethodError、Bean 创建失败、ClassCastException、静态资源 404 等。先讲排查方法,再讲预防手段。排查时最直接的办法是在启动日志里找Caused by最早的位置,当前版本的 Spring Boot 启动失败日志会把"变更配置的建议路径"和"冲突的依赖坐标"打印出来,非常清楚。另外可以用 Maven 的 dependency:tree 命令查看完整的依赖树:

mvn dependency:tree -Dverbose

在输出中观察同一个 groupId:artifactId 是否出现了多个版本,是否是不同传递路径引入的。如果存在版本冲突,优先用 exclusions 排除掉不合理的传递依赖,而不是手动指定其他版本。举个例子,某业务引入了spring-boot-starter-data-mongodb,同时又想用自己已有的 Mongo 驱动版本,它们之间就可能产生版本冲突。正确的做法是先看 Spring Boot 的默认版本是否在你的兼容范围内,是的话就以默认版本为准,只排除传递的那个 jar:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> <exclusions> <exclusion> <groupId>org.mongodb</groupId> <artifactId>mongo-java-driver</artifactId> </exclusion> </exclusions> </dependency>

8.2 版本锁定与重复依赖:项目长期维护的细节

第二类常见问题是重复依赖。我见过有人在 pom.xml 里手动引入 spring-boot-starter-logging 和 log4j2 的依赖,又不排除默认的 logback,结果项目里出现了两个日志门面,日志有时能输出有时完全沉默,排查起来极其痛苦。要避免这种情况,最好遵循一个原则:日志统一由 Spring Boot 管理,需要切换日志实现时,排除掉 starter 里的默认日志依赖,保持全项目只存在一个实现栈。

还有一个维护期非常实用的小工具是 Maven 的 versions-maven-plugin。它可以帮助你检查和批量升级第三方依赖版本:

mvn versions:display-dependency-updates mvn versions:use-latest-releases -Dincludes=com.example:demo-lib

但我不建议在业务忙的时候无脑执行 use-latest-releases,升级第三方依赖前必须看 release notes 和兼容性说明。这个插件更适合作为定期巡检工具,一两个月跑一次,了解有哪些库跟上了安全更新和关键修复。

8.3 如何科学评估是否需要某个依赖

最后想聊一个比技术本身更重要的思维方式。很多人看到某个技术文章推荐了一堆依赖,就忍不住往自己项目里加,结果越加越重、越加越乱。我在选依赖时一般会问自己三个问题:

这个需求在项目中的真实使用频率有多高?如果只是给一个内部管理后台加个导出 Excel 的功能,引入 EasyExcel 是合理的;如果只是临时调试用,就没必要为了一个脚本工具引入新的依赖依赖。

Spring Boot 本身是否已经提供了相关能力?比如需要 JSON 序列化,默认 Jackson 就行,不用额外引入 Gson;需要参数校验,默认 validation 就够,不用自己写校验框架。

第三方依赖的维护状态和社区活跃度怎么样?如果一个依赖半年没有 release、issue 里反馈的问题没人回复,即便功能再诱人也要谨慎,否则将来它不再支持新版本的 Spring Boot,整个项目会陷入进退两难的境地。

9. 常见问题与排查技巧实录

9.1 Starter 依赖引入后 Bean 没有自动配置

这是一个常见到让人无语的问题:引入了某个 starter,但对应的功能没有生效,比如 RedisTemplate 没有被创建、数据源没有自动配置成功。大多数情况下是自动配置的条件不满足。Spring Boot 的自动配置类上基本都是条件注解,比如@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty。你可以这样排查:

在配置类或启动类上临时开启调试:

debug=true

于是启动日志会列出 Positive matches 和 Negative matches,前者是生效的自动配置,后者是未生效的条件。从 Negative matches 里找到对应的自动配置类和缺失条件,基本就能定位。有一次项目引入了 spring-boot-starter-data-redis,但排查时发现原因是 classpath 里存在另一个 Redis 客户端库导致了版本覆盖,让@ConditionalOnClass匹配到不兼容的类。这个日志排查法能大幅缩短定位时间。

9.2 容器启动慢、端口占用诡异问题

如果应用启动时间明显变长,先检查是不是多重启了多余的自动装配。可以用启动日志里的Started Application in x.xxx seconds对比,并在每个自动配置类上暂设断点打点,找到耗时大户。多半是数据源初始化、消息队列连接、安全初始化这类组件在启动阶段做了较多工作。

至于端口占用,Spring Boot 项目在本地跑多个实例时经常遇到。默认端口 8080 被上一个未关闭的应用占用,报Port already in use,这时候要么 kill 进程,要么在 application.yml 里配置server.port=0让系统随机分配端口。后者很适合本地微服务联调。

9.3 依赖升级后行为变化:一个实际案例

我在某个项目里做过一次 Spring Boot 2.7 到 3.2 的升级,最典型的坑是spring.factories自动配置机制被舍弃,改成了AutoConfiguration.imports。如果你在项目里写了自己的 starter 或自动配置类,升级到 Boot 3 后必须把放在META-INF/spring.factories里的自动配置声明迁移到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中,否则自定义配置不会生效。另一个常见变化是javax.*包名换成了jakarta.*,所有 Validation 注解、Servlet API 的 import 路径都要跟着改,不然编译失败。这些升级细节在选依赖阶段可能看不出影响,但长期维护时都要面对。

10. 实操建议与后续扩展思路

每次创建一个新项目,我都会先写好一份打底的依赖清单:spring-boot-starter-web、spring-boot-starter-validation、spring-boot-starter-data-jpa(或 MyBatis Plus)、spring-boot-starter-test、Lombok、MapStruct(如果实体/ DTO 转换频繁)、springdoc-openapi。这一套组合能覆盖绝大多数业务系统的后端骨架需求,配合 spring-boot-starter-parent 的统一版本管理,基本能保证项目从一开始就处于一个稳定、可维护的状态。

后续如果需求演进,再按层次逐步补充:缓存服务接入时加 Redis 相关依赖,多环境配置变复杂时引入配置中心,服务拆分阶段再引入微服务组件。我个人的体会是,依赖选型不是一个一次性决策,而是一个随项目发展不断权衡调整的过程。不必追求首次把所有依赖都选齐,反而控制在满足当前需求的前提下尽量精简,会大大降低长期维护时的复杂度和升级成本。

最后再分享一个小技巧:创建项目前先花 10 分钟确认目标运行时环境(JDK 版本、部署方式、数据库类型),这些因素会直接影响依赖版本选择。比如你准备用 JDK 21 和 Spring Boot 3.2,那所有第三方库的版本就尽量选在 2023 年下半年之后发布的兼容版本,不要贪图旧版本的稳定文档而强行降级核心依赖。骨架搭对了,后面的业务开发会顺畅很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询