
本文详解 spring boot 多模块 gradle 项目中因 @componentscan 配置不当导致 controller 无法被扫描、接口返回 404 的典型问题,并提供完整、可落地的模块结构、依赖管理及启动类配置方案。
本文详解 spring boot 多模块 gradle 项目中因 @componentscan 配置不当导致 controller 无法被扫描、接口返回 404 的典型问题,并提供完整、可落地的模块结构、依赖管理及启动类配置方案。
在构建基于 Spring Boot 的多模块 Gradle 项目时,404 Not Found(如 /tasks/hello 返回 status: 404)往往并非路由或端口问题,而是 Spring 容器未能成功加载控制器(Controller)——根本原因通常是 组件扫描范围配置错误,尤其在跨模块引用时极易踩坑。
? 核心问题定位:@ComponentScan 覆盖默认行为
你的 TaskServiceApplication 类使用了显式 @ComponentScan({"com.nyuro.domain"}),这会完全覆盖 Spring Boot 默认的组件扫描逻辑。而 @SpringBootApplication 本身已等价于 @Configuration + @EnableAutoConfiguration + @ComponentScan,其默认扫描范围是启动类所在包及其子包(即 com.nyuro.taskservice 及其下所有子包,如 com.nyuro.taskservice.controllers)。
但你强制指定仅扫描 com.nyuro.domain,而该包下没有 Controller 类(Controller 在 com.nyuro.taskservice.controllers),导致 Spring 完全忽略你的控制器,因此所有 HTTP 请求均返回 404。
✅ 正确做法:移除显式 @ComponentScan,依赖默认扫描机制
// ✅ 推荐:简洁、安全、符合 Spring Boot 约定
@SpringBootApplication
public class TaskServiceApplication {
public static void main(String[] args) {
SpringApplication.run(TaskServiceApplication.class, args);
}
}
若需额外扫描其他模块(如 domain 中的 @Configuration 或 @Component 类),应显式补充而非替换:
// ⚠️ 仅当 domain 模块含需扫描的 Spring 组件时才添加(通常不需要)
@SpringBootApplication
@ComponentScan(basePackages = {"com.nyuro.taskservice", "com.nyuro.domain"})
public class TaskServiceApplication { ... }
? 提示:@EntityScan 和 @EnableJpaRepositories 同理——除非 domain 模块中定义了 @Repository 或 @Entity 且包路径与主应用不一致,否则无需显式声明。Spring Boot 会自动扫描 @SpringBootApplication 所在包下的 @Entity 和 @Repository。
? 多模块 Gradle 配置最佳实践
1. 根项目 build.gradle(精简基础配置)
plugins {
id 'java'
}
group = 'com.nyuro'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
// 全局依赖(如测试库)可在此统一声明
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
}
test {
useJUnitPlatform()
}
2. domain 模块 build.gradle(纯领域模型,无 Spring 依赖)
plugins {
id 'java'
}
// 仅需 JPA API(非实现),避免引入 Spring Boot 依赖污染
dependencies {
api 'jakarta.persistence:jakarta.persistence-api:3.1.0'
// 若含 Lombok,可添加:compileOnly 'org.projectlombok:lombok'
// testImplementation 'org.junit.jupiter:junit-jupiter-api'
}
3. task-service 模块 build.gradle(Spring Boot 应用)
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.0' apply false // 注意:根项目统一管理版本
id 'io.spring.dependency-management' version '1.1.0'
}
// 启用 Spring Boot 插件(关键!)
apply plugin: 'org.springframework.boot'
group = 'com.nyuro'
version = '0.0.1-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// ✅ 正确引入 domain 模块(无需额外 jar)
implementation project(':domain')
// 数据库驱动按需添加(如 MySQL)
runtimeOnly 'com.mysql:mysql-connector-j'
}
4. settings.gradle(根目录唯一入口)
rootProject.name = 'nyuro-backend' include 'domain', 'task-service' // ❌ 删除 task-service 目录下的 settings.gradle
⚠️ 其他关键注意事项
- Git 忽略规则:.gitignore 必须置于项目根目录,确保忽略 .gradle/, build/, .idea/, *.iml 等生成文件。
- Gradle Wrapper:仅保留根目录的 gradlew,删除子模块中的重复副本。
- 包结构一致性:确保 domain 模块中实体类实际位于 com.nyuro.domain(而非 module 包),否则 @EntityScan 仍需调整路径。可通过 IDE 检查 Task.java 的 package 声明。
- 数据库配置:若 JPA 初始化失败导致应用未完全启动,也会表现为 404(因 Controller Bean 未创建)。建议先注释 spring-boot-starter-data-jpa 和相关配置,验证 Controller 是否正常响应,再逐步恢复数据层。
✅ 验证步骤
- 清理构建:./gradlew clean
- 启动服务:./gradlew :task-service:bootRun
- 检查日志:确认 Mapped URL [/tasks/hello] 出现在控制台(表明 Controller 已注册)
- 发送请求:curl -X GET http://localhost:8080/tasks/hello
遵循以上配置后,Spring 将自动扫描 task-service 下所有组件,domain 模块的实体与依赖将被正确注入,404 问题将彻底解决。多模块设计的核心在于职责分离与依赖收敛,而非过度干预 Spring 的自动装配机制。











