Spring Boot 项目结构怎么组织?controller/service/dao 到底放哪?别再乱放了!
上周,实习生小刘提交了第一个 PR,我点开一看,差点血压升高:
com.example.demo
├── UserController.java
├── UserService.java
├── UserMapper.java
├── OrderController.java
├── OrderService.java
└── DemoApplication.java
所有类平铺在同一个包下!
我问他:“你是不是觉得这样找起来方便?”
他点点头。
“兄弟,现在只有 6 个类,当然方便。等项目有 200 个类时,你会哭着求我教你分包!”
今天,我就用 一个真实可跑的用户管理项目,手把手教你:Spring Boot 项目到底该怎么组织结构?
✅ 包怎么分?
✅ controller/service/dao 放哪?
✅ 为什么不能全放一个包?
✅ 附完整可运行代码,复制就能跑!
🧱 一、先看两种结构对比(图说话)
❌ 错误示范:平铺式(新手常见)
com.example.demo
├── DemoApplication.java
├── UserController.java
├── UserService.java
├── UserRepository.java
├── ProductController.java
├── ProductService.java
└── ProductRepository.java
问题:
- 类一多就眼花缭乱
- 想改用户模块,却翻半天
- 团队协作时互相覆盖代码
✅ 正确示范:按职责分层 + 按模块分包(大厂标准)
com.example.demo
├── DemoApplication.java ← 主启动类
│
├── controller/ ← 所有 Controller
│ ├── user/
│ │ └── UserController.java
│ └── product/
│ └── ProductController.java
│
├── service/ ← 所有 Service
│ ├── user/
│ │ ├── UserService.java
│ │ └── impl/UserServiceImpl.java
│ └── product/
│ ├── ProductService.java
│ └── impl/ProductServiceImpl.java
│
├── repository/ ← 数据访问层(DAO)
│ ├── user/
│ │ └── UserRepository.java
│ └── product/
│ └── ProductRepository.java
│
└── model/ ← 实体类
├── User.java
└── Product.java
✅ 优点:
- 一眼看清项目结构
- 修改用户模块,只动
user/目录- 新人接手 10 分钟上手
🛠️ 二、为什么必须这么分?—— 3 个血泪教训
教训 1:Spring Boot 默认只扫描主启动类的子包!
如果你把 UserController 放在 com.controller(和 com.example.demo 同级),Spring 根本扫不到它 → 启动不报错,但访问 404!
💡 黄金法则:所有业务代码必须是主启动类的子包!
教训 2:接口 + 实现类,必须分开
UserService是接口(定义契约)UserServiceImpl是实现(具体逻辑)- Controller 只依赖接口,不依赖实现 → 方便未来替换(比如 Mock 测试)
教训 3:职责分离,降低耦合
- Controller:只管接收请求、返回响应
- Service:处理业务逻辑
- Repository:只管和数据库打交道
改数据库?只动 Repository!
加缓存?只动 Service!
💻 三、手把手:创建一个规范的 Spring Boot 项目
步骤 1:新建项目(IDEA)
- Group:
com.example - Artifact:
structured-demo - Dependencies: Spring Web
❌ 不要勾选 Thymeleaf、JPA(我们用最简结构)
步骤 2:创建标准目录结构(手动建包)
在 com.example.structureddemo 下创建:
controller
└── user
service
└── user
└── impl
repository
└── user
model
💡 技巧:在 IDEA 中右键包名 → New → Package,输入
controller.user会自动创建两级
步骤 3:编写代码(复制即用)
1. 实体类(model/User.java)
package com.example.structureddemo.model;
public class User {
private Long id;
private String name;
private String email;
// 构造函数、getter、setter(省略,IDEA 可生成)
public User() {}
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// getter/setter...
}
2. Repository(repository/user/UserRepository.java)
package com.example.structureddemo.repository.user;
import com.example.structureddemo.model.User;
import org.springframework.stereotype.Repository;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.Map;
@Repository
public class UserRepository {
private final Map<Long, User> userMap = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
public User save(User user) {
user.setId(idGenerator.getAndIncrement());
userMap.put(user.getId(), user);
return user;
}
public User findById(Long id) {
return userMap.get(id);
}
}
3. Service 接口(service/user/UserService.java)
package com.example.structureddemo.service.user;
import com.example.structureddemo.model.User;
public interface UserService {
User createUser(User user);
User getUserById(Long id);
}
4. Service 实现(service/user/impl/UserServiceImpl.java)
package com.example.structureddemo.service.user.impl;
import com.example.structureddemo.model.User;
import com.example.structureddemo.repository.user.UserRepository;
import com.example.structureddemo.service.user.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public User createUser(User user) {
return userRepository.save(user);
}
@Override
public User getUserById(Long id) {
return userRepository.findById(id);
}
}
5. Controller(controller/user/UserController.java)
package com.example.structureddemo.controller.user;
import com.example.structureddemo.model.User;
import com.example.structureddemo.service.user.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
}
6. 主启动类(默认即可)
package com.example.structureddemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StructuredDemoApplication {
public static void main(String[] args) {
SpringApplication.run(StructuredDemoApplication.class, args);
}
}
▶️ 四、运行验证(30 秒)
- 启动
StructuredDemoApplication - 用 Postman 或 curl 测试:
# 新增用户
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"name":"张三","email":"zhangsan@example.com"}'
# 返回:{"id":1,"name":"张三","email":"zhangsan@example.com"}
# 查询用户
curl http://localhost:8080/api/users/1
💡 五、Bonus:常见问题解答
Q1:一定要分 impl 包吗?
小项目可以不分,但接口和实现类必须分开文件。分
impl是为了清晰。
Q2:model 放 entity 还是 pojo?
Spring Boot 项目一般叫
model或domain。entity多用于 JPA 项目。
Q3:工具类放哪?
新建
util/包,如com.example.demo.util.DateUtil
Q4:配置类放哪?
新建
config/包,如WebConfig.java
💬 六、写在最后
项目结构,不是“好不好看”的问题,而是 “好不好维护” 的问题。
记住三句话:
- 主启动类是根,所有代码是它的子包
- 按职责分层:controller → service → repository
- 按模块分包:user/、order/、product/
做到这三点,你的代码就能从“能跑”变成“值得看”!
互动时间:
你现在的项目是怎么分包的?有没有踩过“404 因为包放错”的坑?
欢迎评论区分享!点赞最高的送《Spring Boot 项目结构规范模板》PDF!
下期预告:
《application.yml 和 application.properties 有什么区别?怎么选?》
👉 关注我,少走弯路,快速进阶!
更多推荐
所有评论(0)