Java 项目结构与分层
源码目录约定与包分层:如何让依赖方向稳定、测试与部署边界清晰。
#concept
#growing
#dev
#java
[!info] related notes
Java 项目结构与分层
这个概念为什么出现
语言会了仍可能把代码堆成“大泥球”。项目结构回答:源码放哪、测试放哪、依赖朝哪指、模块如何拆。
[!abstract] 一句话理解 采用构建工具约定目录;用包/模块表达边界;本篇采用入口依赖应用、应用依赖领域、基础设施实现内层接口的组织方式,避免领域反向依赖 JDBC 等实现细节。
结构示例
场景:订单服务的包草图
src/main/java/shop/order/
api/OrderController.java # 入口适配
app/PlaceOrderService.java # 用例
domain/Order.java # 领域
infra/JdbcOrderRepository.java # 基础设施
src/test/java/shop/order/...
这里画的是源码依赖方向,不是运行时调用顺序:
api → app → domain
infra → domain(实现接口)
app → domain 接口(不直接依赖 JDBC 细节)
应用通过接口调用,运行时仍可能执行 JdbcOrderRepository;外层启动/装配代码负责创建实现并注入应用。这种依赖倒置不会因目录命名而自动生效,需要访问控制、构建模块或依赖检查约束。
结合场景再看三个关注点
- 领域不依赖框架注解也可测(理想)。
- 接口按职责放在应用或领域侧,实现放 infra;不是每个类都需要额外抽接口。
- 测试镜像主包结构。
核心概念与准确模型
1. 标准目录
Maven 与 Gradle Java 插件默认使用 src/main/java、src/test/java,并分别提供 resources 目录。Java package 声明确定命名空间;目录与包名对齐是构建/工程约定,不是目录名自动决定访问权限。
2. 分层 vs 按功能
- 按层:controller/service/repo 全局切
- 按功能:
order/下聚合层——扩展时常更清晰
3. 模块拆分
- 多构建模块(如 order-domain/order-infra)、Java 包、JPMS module 和部署服务不是同一层边界,不应一一等同。
- 控制循环依赖
边界情况与反直觉行为
- 过度微服务拆分早于边界清晰。
- 共享 kernel 变成新泥球。
- 测试只测框架装配不测领域。
常见误区
[!warning] 常见误区:utils 包垃圾桶 无主名责任的 util 膨胀是架构腐烂信号。
工程实践
- ArchUnit 等守护依赖方向(可选)。
- 公共库慎抽。
- README 写清模块职责。
- 与团队统一一种主风格。
本节总结
- 目录约定 + 依赖方向
- 功能包 + 分层思想
- 为 Web/数据访问落地
自测题
- 为什么领域层不要直接依赖 JDBC 驱动类型?
- main/test 分离的收益?
参考答案
- 便于替换持久化与单测,避免基础设施细节泄漏。
- 生产产物不带测试代码,测试可依赖测试范围库。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Maven Standard Directory Layout | 官方 | 目录约定 |
| Gradle Java projects | 官方 | 源集 |