
写微服务项目我这两年从单体一路折腾过来工具链换过好几轮最近半年把主力构建工具从Maven切到了Gradle配合Spring Boot重新搭了一套多模块微服务骨架。这篇博文就把整个搭建过程完整拆开来讲模块怎么划、Gradle怎么配、服务之间怎么通信、构建加速和踩坑点怎么处理全部是能直接拿到项目里用的实操内容。适合正在学微服务、或者准备把单体项目改造成多模块结构的Java开发者新手可以照着一步步来老手可以直接翻到第5章看问题排查。先说清楚这套东西是什么用Gradle管理一个包含多个Spring Boot服务的工程每个业务能力独立成一个子模块顶层通过统一配置控制依赖版本和构建行为。它能解决的主要问题是代码复用混乱、依赖版本冲突、模块之间耦合太深以及传统单工程里改一处全家重启的痛点。下面我从设计思路开始讲全程带代码不整虚的。1. 整体设计先想清楚再敲键盘1.1 多模块和微服务不是一回事很多初学者容易把多模块工程和微服务直接画等号这是个很大的误区。多模块是构建层面的组织方式一个Gradle工程里塞了多个子项目它们共享一套构建配置最终编译成多个Jar包而微服务是运行层面的架构方式强调的是独立进程、独立部署、独立生命周期。两者可以互相配合但不等价。我见过一些团队把几十个服务塞进一个Gradle多模块工程里理由是方便统一管理。结果模块之间依赖越来越深编译一次要跑全量构建部署还是要一个个拆出去完全没有享受到微服务的独立部署红利反而背上了巨型工程的构建负担。所以做这套实战之前首先要确定你的边界哪些模块适合放在同一个Gradle工程里哪些服务应该彻底独立。我的判断标准很简单同一份代码仓库、同一次版本发布、共享同一套研发流程的模块适合放进同一个多模块工程凡是需要独立发布、独立扩缩容、由不同团队维护的建议直接拆仓库。微服务不等于把所有内容物理塞进一个工程里多模块工程更适合承载基础组件加多个业务领域服务这样的中等规模集群。1.2 为什么这个项目选Gradle而不是Maven这个问题几乎每次分享都会被问到。我的回答是Gradle在构建性能、脚本灵活性和依赖缓存上确实比Maven更适合中大型多模块项目。Maven的优势是稳定、生态成熟、大家都会用但它的XML配置冗长一个稍微复杂点的多模块项目pom文件复制粘贴一屏都放不下Gradle用Groovy或Kotlin DSL循环、条件判断、自定义任务是内置能力灵活性高出一截。性能差距在项目变大后非常明显。Gradle的增量构建机制基于task输入输出快照只有文件内容真正变了才会重新执行Maven的增量能力相对粗糙经常出现改了一个模块却触发大范围插件重跑的情况。我实际测试过同一个包含6个子模块的工程冷启动构建Gradle比Maven快大约30%到40%热构建只改单模块差距更大能到好几倍这对日常开发体验影响极大。当然Gradle也不是没有代价它默认不带中央仓库的完整镜像国内网络环境下下载依赖会让人抓狂构建脚本的灵活也意味着更容易写出一坨只可意会不可言传的DSL。这些坑在后面都会提到提前打好预防针就好。1.3 模块边界三个维度确定该怎么拆动手创建模块之前我习惯先用三个维度过一遍业务部署维度、复用维度和变更频率维度。部署维度决定哪些组件必须独立成服务比如用户服务、订单服务如果后续要单独扩容就应该各自独立复用维度决定哪些代码应该下沉成公共模块比如统一返回体、异常处理、工具类这类代码如果复制到每个服务里后期改动就是全量复制明显不合理变更频率维度决定哪些模块应该依赖稳定比如API接口定义模块应该尽量少变一旦频繁变动消费方就得跟着折腾。在这个实战中我把工程分成了五个模块结构如下common基础公共代码包括统一响应体、统一异常、工具类被所有模块依赖但不依赖任何具体业务。api各服务对外暴露的Feign接口定义以及DTO传输对象。这个模块让服务之间可以脱敏依赖。user-service用户领域服务负责用户相关业务以及用户数据接口的实际返回。order-service订单领域服务负责订单业务通过api模块调用用户服务。bootstrap拆出来的启动模块集中管理启动类和配置文件。这里有个在设计时容易被低估的点bootstrap这个模块。把启动类放在顶层或者某个业务模块里短期内没啥问题但只要涉及多个服务需要独立配置、独立端口杂乱的启动类会让整个工程越来越难管理。拆出独立的启动模块每个子服务对应一个启动类配置文件通过profile区分看起来多了一层实际上部署和排查轻松很多。2. 环境准备与Gradle核心配置2.1 JDK和Gradle版本怎么选才不打架从源头说Gradle对JDK版本有自己支持的矩阵不是随便配个最新JDK就能跑。以当前环境为例Spring Boot 3.x要求Java 17起步我用的是JDK 17Gradle则需要选择7.6.1以上版本旧版Gradle跑在JDK 17上要么直接报错要么某些插件行为异常。推荐做法是Gradle 8.x配JDK 17或21Spring Boot 3.2.x配合使用非常稳定。安装这块Linux和macOS用户我推荐用SDKMAN管理JDK和Gradle版本一行命令切换不用把环境变量改来改去。Windows用户建议直接下载Gradle的zip包解压然后配置GRADLE_HOME和PATH环境变量。很多人卡在环境变量上记住两条一是PATH里要追加%GRADLE_HOME%\bin二是配置完要重开终端然后运行gradle -v验证。提示JDK 17安装后建议顺手配置JAVA_HOME。Gradle在构建时会优先去找JAVA_HOME如果你本机装了多个JDK版本不把JAVA_HOME指清楚构建时很可能莫名其妙报Unsupported class file major version。2.2 国内开发者必做的镜像加速配置Gradle默认依赖中心是Maven Central和Gradle Plugin Portal在国内网络环境下下载慢、失败率高是常态。这个不做镜像配置后面每一步都会很痛苦。我的经验是把镜像配到三个地方全局初始化脚本、项目的仓库配置、插件仓库配置。全局脚本可以直接加快所有项目的下载速度。在用户目录下创建~/.gradle/init.gradleWindows用户是C:\Users\你的用户名\.gradle\init.gradle内容如下allprojects { repositories { maven { url https://maven.aliyun.com/repository/public } maven { url https://maven.aliyun.com/repository/gradle-plugin } maven { url https://maven.aliyun.com/repository/spring } mavenLocal() mavenCentral() } buildscript { repositories { maven { url https://maven.aliyun.com/repository/public } maven { url https://maven.aliyun.com/repository/gradle-plugin } } } }需要说明的是init.gradle这种全局镜像配置适合个人开发机如果项目里有多人协作更推荐把镜像写进项目自己的build.gradle里这样大家拉代码后配置自动生效不用各自折腾。项目里的配置我会在下一节给出。国内镜像源除了阿里云腾讯云也提供了maven镜像地址是https://mirrors.cloud.tencent.com/nexus/repository/maven-public/。我自己实测阿里云源的稳定性和同步速度都更胜一筹正常情况下作为主选没有问题。如果你的团队有私服比如Nexus建议把私服地址也配到仓库列表里顺序放在阿里云之前。2.3 根工程配置Settings和构建脚本的分工创建好工程目录后第一件事是写settings.gradle。Gradle从8.x开始默认使用.gradle后缀老版本用的是.gradle的同名文件也没问题。多模块工程的settings.gradle核心内容是仓库地址、插件仓库、模块声明以及可选的点版本管理配置pluginManagement { repositories { maven { url https://maven.aliyun.com/repository/gradle-plugin } maven { url https://maven.aliyun.com/repository/public } gradlePluginPortal() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { maven { url https://maven.aliyun.com/repository/public } maven { url https://maven.aliyun.com/repository/spring } mavenLocal() mavenCentral() } } rootProject.name microservice-demo include common include api include user-service include order-service include bootstrap这里两个细节值得展开dependencyResolutionManagement里的FAIL_ON_PROJECT_REPOS模式很关键它强制所有模块的依赖仓库统一归根工程管理防止哪个子模块自己写个仓库地址导致构建行为分裂pluginManagement则统一管插件下载源。这些细节决定了你的构建环境是否可复现团队协作时尤其重要。根目录下的build.gradle不直接引用Spring Boot插件而是用apply false把插件下载到本地由各子模块按需启用plugins { id java id org.springframework.boot version 3.2.4 apply false id io.spring.dependency-management version 1.1.4 apply false } allprojects { group com.example version 1.0.0-SNAPSHOT } subprojects { apply plugin: java apply plugin: io.spring.dependency-management java { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } configurations { compileOnly { extendsFrom annotationProcessor } } dependencies { compileOnly org.projectlombok:lombok annotationProcessor org.projectlombok:lombok testCompileOnly org.projectlombok:lombok testAnnotationProcessor org.projectlombok:lombok } tasks.withType(JavaCompile).configureEach { options.encoding UTF-8 } tasks.named(test) { useJUnitPlatform() } }注意根build.gradle这里已经统一给所有子模块注入了Lombok依赖和注解处理配置这是多模块工程最容易出问题的点之一。很多人每个模块重复写Lombok依赖漏掉了注解处理配置导致编译通过但运行时报NoClassDefFoundError后面第5章我会专门讲。2.4 用Version Catalog管理依赖版本告别版本地狱多模块项目最怕什么依赖版本不统一。团队里不同人引了不同版本的Jackson或MyBatis-Plus编译时大家都好运行时就各种诡异。Gradle从7.0开始推荐使用Version Catalog来集中管理版本号这个机制比Maven的dependencyManagement更直观也比手工写一堆ext变量更规范。在gradle/libs.versions.toml文件里定义版本和依赖[versions] spring-boot 3.2.4 spring-cloud 2023.0.1 spring-cloud-alibaba 2023.0.1.0 mybatis-plus 3.5.5 mysql-connector 8.3.0 lombok 1.18.32 [libraries] spring-boot-starter-web { module org.springframework.boot:spring-boot-starter-web } spring-boot-starter-test { module org.springframework.boot:spring-boot-starter-test } spring-boot-starter-actuator { module org.springframework.boot:spring-boot-starter-actuator } spring-cloud-starter-openfeign { module org.springframework.cloud:spring-cloud-starter-openfeign } spring-cloud-starter-alibaba-nacos-discovery { module com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery } spring-cloud-starter-alibaba-nacos-config { module com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config } mybatis-plus-spring-boot3-starter { module com.baomidou:mybatis-plus-spring-boot3-starter, version.ref mybatis-plus } mysql-connector-j { module com.mysql:mysql-connector-j, version.ref mysql-connector } lombok { module org.projectlombok:lombok, version.ref lombok } [bundles] spring-boot-web [spring-boot-starter-web, spring-boot-starter-actuator]子模块里再也不需要写带版本号的依赖坐标比如user-service的依赖段落就变成dependencies { implementation project(:common) implementation project(:api) implementation libs.spring.boot.web implementation libs.spring.cloud.starter.alibaba.nacos.discovery implementation libs.mybatis.plus.spring.boot3.starter runtimeOnly libs.mysql.connector.j testImplementation libs.spring.boot.starter.test }这种写法的好处是升级版本只需要改toml文件全局生效不会再出现某个子模块用老版本、另一个模块用新版本的问题。顺便提一句如果你从Maven迁移过来Maven的properties功能就是Version Catalog要替代的东西但Version Catalog不只是版本变量它还能定义依赖组合bundle对常见starter集合的复用非常方便。3. 核心模块落地从公共代码到启动模块3.1 common模块统一响应、异常处理和工具类任何微服务工程公共模块先打底。我设计的common模块不依赖任何Spring Boot业务组件只依赖基础库和Lombok这样其他模块引用它的时候不会把一堆无用的starter带过来构建速度和依赖纯净度都好很多。先定义统一响应体这里有个设计细节泛型加上success、code、message、data四个字段比直接用Map返回多了类型安全保障也会让Swagger接口文档自动生成时结构更加明确。Data public class ResultT implements Serializable { private int code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(int code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }异常处理这块我强烈建议在common模块里放一个全局异常处理器。不放在业务服务里是因为每个服务都要用写在一个地方然后让所有服务引入能保证错误结构一致。常见的处理思路是RestControllerAdvice捕获业务异常、参数校验异常、兜底异常分别转成统一响应体返回。处理的时候注意日志要打完整异常栈线上排查问题全靠它。工具类方面我一般放三类JSON工具封装基于Jackson不选Fastjson的原因是它历史漏洞偏多、Bean拷贝工具封装基于Spring的BeanUtils包一层可以方便替换、以及一些日期和字符串处理的静态方法。工具类的设计原则是不能太厚不要什么方法都往里塞否则又会变成一个上帝类后期没人敢碰。3.2 业务服务模块User-Service和Order-Service的落地姿势业务模块是整个工程的核心。以user-service为例它的build.gradle需要依赖common、api模块以及数据库、MyBatis-Plus等数据访问组件。注意这里有个容易搞反的依赖方向业务模块依赖api模块但api模块不能依赖业务模块否则就形成循环依赖这是多模块工程的大忌。user-service内部按经典的controller-service-mapper来分包com.example.user ├── controller │ └── UserController.java ├── service │ ├── UserService.java │ └── impl │ └── UserServiceImpl.java ├── mapper │ └── UserMapper.java └── entity └── User.java启动类放在user-service里还是bootstrap里我的做法是工程前期放在自己模块内方便快速启动调试等集群服务多了统一迁到bootstrap。最终我推荐拆出bootstrap模块的原因之一是你把启动类放在user-service里的话bootstrap就没有存在的意义了。后面第3.4节会统一说明启动模块改造。Service层的接口和实现拆分这里我提一个建议不要每个Service都强制拆接口。如果这个服务只有一种实现、不打算做多态替换直接写一个具体的类反而更清晰。实践中有太多人为了架构规范写了接口和impl两个文件结果接口永远只有一个实现形成无意义的间接层。这个工程里我保留了接口是为了和前端暴露的内部Feign接口对应逻辑上更严谨但如果你的服务内部逻辑不复杂完全可以直接写实现类。3.3 api模块服务间调用的接口契约服务间调用最怕的是各自对着数据库查资源、或者A服务直接依赖B服务的Mapper实现。高耦合的典型表现是B服务表结构一变A服务的SQL全挂。引入api模块就是为了在服务之间建立契约被调方实现这个接口调用方只依赖接口定义。在api模块里定义一个Feign客户端接口比如user-service提供给外部的用户查询能力FeignClient(name user-service, contextId userApiClient) public interface UserApiClient { GetMapping(/api/user/info/{id}) ResultUserInfoDTO getUserInfo(PathVariable(id) Long id); }对应的DTOUserInfoDTO也放在api模块里只包含传输需要的字段不暴露内部实体结构。这样即使user-service内部的用户表加了一个deleted字段、或者把密码相关字段做了改造只要接口和DTO不变对调用方来说就是无感升级。DTO放哪需要刻意控制。很多团队会把DTO、VO、BO分层搞得特别细每个模块都要建一堆类实际效果往往是类爆炸。我建议对外传输统一用api模块的DTO对内逻辑处理可以用实体对象能合并就不强行拆分避免为分层而分层。3.4 bootstrap启动模块启动类和配置文件的集中管理当服务数量超过两三个启动类分散在各业务模块里部署时就得记住哪个类在哪个模块多环境配置文件也容易搞得互相覆盖非常不直观。我的方案是把所有启动类放到bootstrap模块同时把多环境配置文件也集中在这里。bootstrap的build.gradle逻辑对每个业务模块依赖同时启用Spring Boot插件。这里有一个关键点哪个模块的build.gradle应用了org.springframework.boot插件哪个模块就会被构建成可执行Jar。所以我把这插件只加在bootstrap模块上其他业务模块不应用这样打出来的包就天然是可运行的服务Jar 依赖的普通Jar运行和部署逻辑非常清晰。plugins { id java id org.springframework.boot id io.spring.dependency-management } dependencies { implementation project(:user-service) implementation project(:order-service) // 其他依赖 }启动类示例SpringBootApplication EnableDiscoveryClient public class UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.class, args); } }配置文件集中到bootstrap模块的src/main/resources下通过文件名区分服务和环境比如application.yml公共配置application-user.yml用户服务配置application-order.yml订单服务配置application-user-dev.yml用户服务开发环境启动时通过--spring.profiles.activeuser,dev组合激活。这种设计一开始看起来比传统单模块多了点复杂度但服务一多它的好处就出来了找配置不用翻半天目录部署脚本里只要指定profile剩下的全部统一。4. 微服务化落地注册发现、远程调用和监控集成4.1 Nacos服务注册与配置服务发现怎么做微服务之间要通信第一步是让对方能找到彼此。user-service和order-service都注册到注册中心调用方根据服务名去发现地址而不是在配置文件里写死IP。这里我选的是Nacos相比Eureka和ConsulNacos同时提供注册中心和配置中心在国内生态里用得最广和Spring Cloud Alibaba集成也最顺畅。引入依赖后在application.yml里配置服务名和注册中心地址spring: application: name: user-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: dev启动类上加上EnableDiscoveryClient注解Spring Cloud新版里其实可以省略但显式标注更明确团队里有人用旧版框架时也不会踩坑。启动服务后在Nacos控制台能看到两个服务都上线了这时就可以验证服务间的调用。配置中心的使用建议把每个服务的非敏感配置放到Nacos的配置列表中比如线程池参数、开关类配置、限流阈值。这些配置变更频率高放在Nacos里可以动态刷新不用改代码重启。敏感配置数据库密码、秘钥等建议用Nacos的加密插件或者直接用环境变量注入不要明文放在配置文件里提交到代码仓库。4.2 OpenFeign远程调用服务间通信的正确打开方式服务注册好之后接下来是实现服务之间的调用。同步调用我首选OpenFeign它能直接基于接口定义生成HTTP客户端天然契合前面api模块的接口设计。底层走HTTP虽然性能不如直接HTTP Client但胜在开发效率高、代码直观配合负载均衡组件可以自动做服务实例间的负载均衡。调用方只需要把api模块中的UserApiClient注进来像调用本地方法一样调用即可Service RequiredArgsConstructor public class OrderServiceImpl implements OrderService { private final UserApiClient userApiClient; Override public OrderVO createOrder(OrderCreateRequest request) { ResultUserInfoDTO userResult userApiClient.getUserInfo(request.getUserId()); if (userResult.getCode() ! 200) { throw new BizException(userResult.getCode(), userResult.getMessage()); } // 创建订单业务逻辑 return orderVO; } }用OpenFeign有几个细节必须处理。第一个是超时配置默认连接超时只有10秒内部调用通常要压到2到3秒避免拖垮对端服务。第二个是错误处理Feign在HTTP非2xx状态时会抛异常要统一捕获转成业务异常不能让它直接暴露给前端。第三个是路径动态变量、参数对象序列化等边界情况建议在api模块里针对每个接口写一个冒烟测试确保接口调用方和被调方序列化规则一致。feign: client: config: default: connectTimeout: 3000 readTimeout: 50004.3 Actuator与Micrometer监控微服务可观测的基础服务上线之后怎么知道它还活着、指标正常不正常Spring Boot Actuator是自带的监控端点方案Micrometer则是统一的指标收集门面。新版Spring Boot 3生成端点时默认只暴露health其他敏感端点需要手动开启这也是网上很多Actuator漏洞问题的根源——生产环境把env、metrics、beans这些端点直接暴露出来了。我推荐的生产环境暴露策略是只开health、info、prometheus三个端点并且加上认证或网络隔离。配合Micrometer注册表可以输出Prometheus格式指标明确暴露哪些、可以有哪些敏感信息先要自己想清楚。看一个build.gradle里的依赖配置implementation org.springframework.boot:spring-boot-starter-actuator implementation io.micrometer:micrometer-registry-prometheus对应的application.yml配置management: endpoints: web: exposure: include: health,info,prometheus endpoint: health: show-details: never这块在微服务体系里属于锦上添花但必须做的部分。没有监控的服务就像开夜车没开大灯系统哪天挂了大概率是运维先发现然后通知你而不是你自己提前发现。Actuator加Prometheus加Grafana是一条成熟的监控链路先把基础端点配好后面接入告警就顺理成章。5. 构建加速与常见问题排查5.1 Gradle构建慢先别骂Gradle每次做完一个多模块工程总会有人反馈Gradle构建好慢但其实八成不是Gradle的问题。先说构建加速的几个有效手段我实际用下来提升非常明显。第一个是开启构建缓存并配置远程缓存。Gradle构建缓存默认是本地开启的同一台机器重复构建直接命中团队场景配置远程缓存后A机器构建过的结果B机器可以直接复用效果从几分钟缩到几秒。配置远程缓存需要额外搭服务小团队可以先不开本地缓存也够用。第二个是善用并行构建和按需配置。在gradle.properties里加上org.gradle.daemontrue org.gradle.paralleltrue org.gradle.cachingtrue org.gradle.configureondemandtrue其中daemon是常驻进程避免每次构建都启动JVMparallel让子模块能并行构建configureondemand只配置和当前任务相关的模块对大型多模块工程很有效。我遇到过有些项目不开并行构建一次要跑四五分钟开了之后直接降到两分钟左右。第三个是JDK和Gradle自身的问题。如果你还在用Java 8跑Gradle 8.x那你会遇到大量不兼容警告甚至报错如果条件允许建议直接用JDK 17或21配合最新稳定版Gradle整体启动和编译速度都有明显提升。这不是玄学是Gradle官方在每个版本里都对较新JDK做了专门的优化。5.2 依赖冲突和Maven和Gradle双修的坑多模块项目依赖冲突是最常见的问题常见症状是运行时报NoClassDefFoundError、ClassNotFoundException、或者NoSuchMethodError。Gradle的依赖解析策略默认是取最高版本这看起来很美好但真实世界里往往存在传递依赖和直接依赖打架的情况。排查时先看依赖报告gradle :user-service:dependencies --configuration runtimeClasspath这个命令会列出所有依赖树你可以快速定位某个jar来自哪个链路也能看到版本冲突后最终用了哪个版本。如果看到冲突合理做法是直接在需要的模块里显式声明依赖版本覆盖掉传递依赖的不确定版本。还有一个我踩过很多次的坑工程里同时存在Maven和Gradle两套构建脚本比如项目从Maven迁移到Gradle旧同事往往习惯手动往本地Maven仓库mvn install新构建脚本配置了mavenLocal()于是把本地仓库里的过期版本依赖给解析进来导致发布到服务器后运行的是旧代码。处理方案是迁移期间把mavenLocal()排在仓库列表最后环境稳定后直接去掉避免本地仓库污染。5.3 Lombok与注解处理器在多模块下的配置陷阱细心的读者应该注意到我在根build.gradle里用configurations { compileOnly { extendsFrom annotationProcessor } }这个配置对Lombok来说至关重要。Lombok在编译期通过注解处理器生成getter、setter、builder等方法如果你只在依赖里加了compileOnly但没把Lombok加到annotationProcessor配置里编译时IDE可能没问题因为IDE工具默认会补上注解处理但一旦通过Gradle命令行或者CI构建就会报找不到getter/setter方法特别典型的是cannot find symbol错误。多模块环境下这个问题经常出在传递依赖上common模块依赖了Lombok业务模块通过implementation project(:common)间接依赖了它但Lombok的注解处理器不会顺着传递依赖跑到业务模块里去。这就是为什么我建议在根subprojects里统一配好Lombok依赖和注解处理而不是每个模块各自做一旦有人忘了配就得白白排查半天。另外使用了MyBatis-Plus的项目要注意它的分页插件和MyBatis注解在某些版本里和Lombok搭配时编译期会出现奇怪的NoSuchMethodError。遇到这种情况先逐项检查版本兼容性一般不是Lombok本身的问题而是版本组合太新或太旧。借助gradle dependencies报告能快速定位。5.4 Deprecated GFeatures警告和版本兼容处理构建日志里出现过一段英文警告大意是Deprecated Gradle features were used in this build, making it incompatible with Gradle X.0很多新手看到之后不知所措其实它的意思是当前构建脚本中用了某个将在未来版本被移除的旧API或旧配置方式当前版本还能跑但升级Gradle之后可能就编译不过了。处理这个警告的方法很直接gradle build --warning-mode all跑一次详细日志会精确告诉你哪个脚本的第几行用了废弃特性。常见来源包括旧式依赖语法、不再推荐的自定义任务写法、某些插件对老Gradle API的调用。拿到具体去改就行改完之后警告消失未来升级Gradle就会顺畅很多。如果这个警告来自第三方插件你又没办法改插件的源码一个临时方案是锁定Gradle版本不升级等插件更新了再统一处理。别为了消警告硬上高版本Gradle有时候第三方插件还没适配升级会引入新的兼容问题破坏比收益大。5.5 多模块工程下MyBatis-Plus和Mapper扫描的边界最后提一个框架集成上常见的坑MyBatis-Plus扫描Mapper时默认只扫描启动类所在包及其子包。多模块工程里如果启动类在bootstrap模块而Mapper在user-service模块的com.example.user.mapper包下直接把MapperScan(com.example.user.mapper)写在bootstrap的启动类上是扫描不到user-service里那些Mapper的。我自己踩过之后总结的解决方案有两种。第一种是在每个业务模块自己的配置类上用MapperScan标注这样各模块自行管理扫描路径启动类不需要关心业务模块内部的Mapper位置第二种是在bootstrap的启动类上同时写多个扫描路径比如MapperScan({com.example.user.mapper, com.example.order.mapper})这样能一次扫描多个包。第一种方式我更推荐因为它让模块自治新增业务服务时不用去改启动类。关于MyBatis-Plus配置还有一点值得注意多模块里公共配置比如逻辑删除、乐观锁插件放common模块后业务模块要确保MyBatis-Plus的starter确实被引入否则插件配置写好了也不生效。建议在业务模块的配置类上写上这些插件同时加上Configuration注解集成在Spring启动流程里。写在最后的几个建议这套工程从无到有搭建下来绕过的坑确实不算少。我最想提醒的是不要为了“多模块”而强行拆分也不要为了“微服务”而把所有东西全部服务化。项目规模小、团队也不大的时候单体加模块化就是最优解等业务确实出现独立部署、独立扩展的需求时再按本文的思路逐步演进。技术选型永远是为业务服务的不管是Gradle还是Maven、多模块还是微服务把问题解决得干净利落才是王道。最后分享一个小技巧在每个模块的构建脚本里加上下面这段当模块越来越多时可以快速打印每个子模块的依赖坐标排查模块间依赖关系十分好用subprojects { task printDependencies { doLast { println ${project.path} - ${configurations.runtimeClasspath.collect { it.name }} } } }我用下来已经习惯每次接手新工程先跑一遍对整个依赖全景心里有数之后再动手改代码。希望你也能少走点弯路。