上周,实习生小刘提交了第一个 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 秒)

  1. 启动 StructuredDemoApplication
  2. 用 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 有什么区别?怎么选?》
👉 关注我,少走弯路,快速进阶!

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐