人人都会AI编程

2.3 标准项目分层结构与命名规范

更新时间:2026-07-10

在搭建完项目骨架之后,代码如何组织便成为第一个架构决策。Spring Boot 本身并没有强制分层规范,但行业实践中形成了一套清晰的分层模型,既能满足单体应用的需求,也便于未来向微服务演进。遵循这套约定,可以让团队成员快速理解代码归属,降低沟通成本。

2.3.1 通用分层架构

最经典的分层方式是将应用划分为 表示层、业务层、数据访问层,并辅以通用工具层和领域模型层。在 Spring Boot 项目中,通常对应以下包结构:

com.example.demo
├── controller          # 表示层 - REST API
├── service             # 业务层 - 接口定义
│   └── impl            # 业务层 - 实现类
├── repository          # 数据访问层 - Repository 接口
│   └── entity          # 持久化对象(PO)
├── model               # 通用模型(DTO、VO、请求/响应对象)
│   ├── dto
│   └── vo
├── config              # 配置类
├── common              # 通用工具、异常、常量
│   ├── exception
│   ├── utils
│   └── constant
├── security            # 安全相关(可选)
└── DemoApplication.java

各层的职责与边界如下:

1. controller 层(表示层)

负责接收 HTTP 请求,进行参数校验,调用业务层,并封装响应结果。Controller 应该尽可能“薄”,不包含业务逻辑,只做流程编排和数据转换。

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public ResponseEntity<UserVO> create(@Valid @RequestBody UserCreateRequest request) {
        UserVO user = userService.createUser(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(user);
    }
}

关键原则:

  • 使用 @Valid@Validated 触发 Bean Validation,在进入业务层之前拦截非法输入。
  • 参数与返回类型应使用专门的前端交互对象(如 XxxRequestXxxResponseXxxVO),绝不直接暴露持久化实体。

2. service 层(业务层)

封装核心业务逻辑,是应用中最重要、代码量最大的层。通常分为接口和实现两个包,接口定义契约,实现类承载具体逻辑。

// 接口
public interface UserService {
    UserVO createUser(UserCreateRequest request);
    UserVO getUserById(Long id);
}

// 实现
@Service
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    @Transactional
    public UserVO createUser(UserCreateRequest request) {
        if (userRepository.existsByUsername(request.getUsername())) {
            throw new BusinessException("用户名已存在");
        }
        UserEntity entity = new UserEntity();
        entity.setUsername(request.getUsername());
        entity.setPassword(passwordEncoder.encode(request.getPassword()));
        userRepository.save(entity);
        return UserConverter.toVO(entity);
    }
}

原则:

  • 事务边界定义在 service 层,使用 @Transactional 通常标注在实现类的公共方法上。
  • 业务校验与规则集中在此层,不要泄漏到 controller 或 repository 中。
  • 调用外部服务、消息发送等也应在 service 层完成,保持 controller 的单一职责。

3. repository 层(数据访问层)

负责与数据库交互,Spring Data JPA 下通常为接口,无需实现类,框架自动代理。

public interface UserRepository extends JpaRepository<UserEntity, Long> {
    boolean existsByUsername(String username);
    Optional<UserEntity> findByUsername(String username);
}
  • 方法命名遵循 Spring Data 的关键字规则(findBy..., existsBy...),复杂查询可使用 @Query
  • 只承担 CRUD 操作,不包含业务逻辑(例如密码加密不应在此层)。
  • 若使用 MyBatis,则对应 mapper 包,Mapper 接口功能类似,但需搭配 XML 或注解定义 SQL。

4. model 层与数据对象划分

真实项目中,内部使用的数据结构非常多样,应在命名上明确区分:

  • Entity / PO(Persistent Object):与数据库表映射的持久化对象,放在 repository.entityentity 包。
  • DTO(Data Transfer Object):服务间或模块间传输数据的对象,放在 model.dto
  • VO(View Object):返回给前端的视图对象,放在 model.vo,可根据前端需求裁剪字段,不应直接返回 Entity。
  • Request / Response:用于接收请求参数和封装响应体,通常放在 model.requestmodel.response,或者直接放在 controller 包内的 request/response 子包。

这种划分起初会带来转换工作,但有效隔离了前端展示、接口协议和数据库设计的变动,长期收益显著。

5. config 层

集中存放 @Configuration 类,用于自定义 Bean、拦截器注册、过滤器配置、跨域设置、安全策略等。

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**").allowedOrigins("*");
    }
}
  • 将配置分散到多个按功能命名的配置类(WebConfigSecurityConfigRedisConfig),不要让一个类承载所有配置。

6. common 包

存放应用级工具类、自定义异常、常量定义、统一响应体等横切基础设施。

// 统一响应体
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;
    // 静态工厂方法 success(), error()...
}

// 自定义业务异常
public class BusinessException extends RuntimeException {
    private final int code;
    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }
}
  • 全局异常处理器(@ControllerAdvice)也可放在 common.exception 中。

2.3.2 命名规范与约定

分层结构只是骨架,一致性命名才能让项目“开口说话”。下表给出最常用的约定:

| 元素 | 规范 | 示例 |
|------|------|------|
| 包名 | 全小写,点分隔,用单数形式 | com.example.demo.controller |
| Controller 类 | 名词 + Controller | UserControllerOrderController |
| Service 接口 | 名词 + Service | UserService |
| Service 实现 | 名词 + ServiceImpl | UserServiceImpl |
| Repository 接口 | 名词 + Repository | UserRepository |
| 实体类 | 名词,可加 Entity 后缀 | UserUserEntity |
| 请求对象 | 功能 + Request | UserCreateRequestOrderQueryRequest |
| 响应对象 | 功能 + Response/VO | UserResponseUserVO |
| 工具类 | 名词 + Utils/Helper | DateUtilsJwtHelper |
| 配置类 | 模块名 + Config | WebConfigSecurityConfig |
| 常量类 | 领域 + Constants | RedisConstantsApiConstants |
| 方法名(Service) | 动词 + 名词,体现业务操作 | createUser()cancelOrder() |
| 方法名(Repository) | findBy/query/list/count + 字段 | findByUsername()listByStatus() |

需要注意的是,这些并非硬性教条,团队可以有自己的变体,但必须保证项目内统一。例如有人喜欢将 DTO 直接放在 service 层下,只要能自圆其说且全员遵守,就没有问题。

2.3.3 一个真实的项目包结构实例

以下是一个简化版电商应用 shop 的完整包树,供参考:

com.example.shop
├── ShopApplication.java
├── controller
│   ├── ProductController.java
│   ├── OrderController.java
│   └── request
│       ├── OrderCreateRequest.java
│       └── ProductSearchRequest.java
├── service
│   ├── ProductService.java
│   ├── OrderService.java
│   └── impl
│       ├── ProductServiceImpl.java
│       └── OrderServiceImpl.java
├── repository
│   ├── UserRepository.java
│   ├── ProductRepository.java
│   ├── OrderRepository.java
│   └── entity
│       ├── UserEntity.java
│       ├── ProductEntity.java
│       └── OrderEntity.java
├── model
│   ├── vo
│   │   ├── UserVO.java
│   │   └── ProductVO.java
│   └── dto
│       ├── OrderDTO.java
│       └── StockDTO.java
├── config
│   ├── WebConfig.java
│   ├── RedisConfig.java
│   └── SecurityConfig.java
├── common
│   ├── ApiResponse.java
│   ├── BusinessException.java
│   ├── GlobalExceptionHandler.java
│   └── utils
│       └── JwtUtils.java
└── security
    ├── JwtTokenProvider.java
    └── UserDetailsServiceImpl.java

2.3.4 分层架构的演进考量

当业务复杂度增长,单体分层结构可能逐步膨胀。但不必过早进行物理拆分,先在模块级别进行纵向分割更为务实:

  • 若订单模块与商品模块相对独立,可以在 com.example.shop 下创建 orderproduct 两个顶层包,各自内部再沿用 controller/service/repository 子结构。
  • 当模块间需要明确边界时,可借助 Spring Modulith 或直接使用 Gradle/Maven 多模块项目,将 order 和 product 拆分为独立模块,但保持共享同一个代码库。

分层与命名的最终目的是让代码在“看到”的那一刻就传递出准确的信息。一个刚接手维护的开发者,打开 OrderServiceImpl 时能立刻感知到它属于业务层、承载订单相关的核心逻辑,而不会迷失在一堆没有规则的类名中。正是这些细小的约束,奠定了工程化开发的基础。

在下一节中,我们将基于这套分层结构,深入 Spring Boot 的自动配置原理,理解为什么在添加了某些 starter 后,无需编写任何配置就能直接运行。