一、先看目录长什么样
很多 Java 项目(尤其是 Spring Boot 项目)的 Service 层会这么组织:
rust
service/
├── itf/
│ └── BannerV2Service.java // 接口:只声明方法
└── impl/
└── BannerV2ServiceImpl.java // 实现类:真正写业务逻辑
这两个文件夹的定位非常清晰:
| 目录 | 全称 | 放什么 | 例子 |
|---|---|---|---|
itf |
interface | 接口(只声明方法,不写具体逻辑) | BannerV2Service.java |
impl |
implementation | 实现类(真正写业务代码) | BannerV2ServiceImpl.java |
关系图大概长这样:
markdown
itf/BannerV2Service ← 约定「能做什么」
▲
│ implements
│
impl/BannerV2ServiceImpl ← 具体「怎么做」
二、它解决什么问题?
1. 依赖接口,不依赖实现
Controller 层不直接依赖 impl 里的实现类,而是依赖 itf 里的接口:
kotlin
@RestController
public class BannerV2Controller {
@Autowired
private BannerV2Service bannerV2Service; // 这是接口,不是实现类
}
这样做的好处是:Controller 只关心"能做什么",不关心"怎么做" 。
比如 bannerV2Service.saveOrUpdateBanner(...),Controller 只知道调用这个方法就能保存 Banner,至于里面是:
- 直接插入数据库?
- 先查缓存再写库?
- 调用别的微服务?
Controller 完全不用管。
2. 一眼分清「有哪些能力」和「怎么实现」
打开 itf/BannerV2Service.java,你看到的是一份干净的 API 清单:
scss
public interface BannerV2Service {
Message saveOrUpdateBanner(BannerV2DTO bannerV2DTO);
BannerV2DTO getBanner(Long id);
Page<BannerV2DTO> getBanners(...);
}
而打开 impl/BannerV2ServiceImpl.java,你看到的是具体实现细节:校验、拼参数、调 Mapper、处理异常......
这种拆分让代码更可读、可维护。
3. 方便测试和替换
写单元测试时,你可以轻松 Mock 接口:
scss
BannerV2Service mockService = mock(BannerV2Service.class);
when(mockService.getBanner(1L)).thenReturn(mockBanner);
不需要真的去连数据库、启动容器。
如果将来业务需要换一套实现(比如从本地缓存换成 Redis),只需要再写一个 BannerV2RedisServiceImpl,实现同一个接口即可,Controller 一行代码都不用改。
三、和前端"接口"的区别(这个很容易搞混)
很多前端同学看到 interface 会条件反射想到 TypeScript 里的:
csharp
interface BannerForm {
imgUrls: string[];
gifUrls?: string[]; // 加字段改这里
}
但在 Java 里,itf 里的接口和前端 TS 里的 interface 是两码事:
| 对比项 | 前端 TS 的 interface | Java 的 Service 接口(itf) |
|---|---|---|
| 用途 | 描述数据长什么样(字段) | 描述能调用哪些方法 |
| 更像 Java 里的 | DTO / VO | Service 接口 |
改 gifUrls 时 |
要加字段 | 通常不用改 |
记住一句话:
Java 的
itf≈ "有哪些 API 方法"前端的
interface≈ Java 的 DTO(数据传输对象)
四、一张表总结
| 角色 | 路径/文件 | 改不改 gifUrls |
为什么 |
|---|---|---|---|
| Service 接口 | itf/BannerV2Service.java |
不改 | 方法签名没变,还是传入 BannerV2DTO |
| Service 实现 | impl/BannerV2ServiceImpl.java |
要改逻辑 | 真正处理 gifUrls 的地方 |
| DTO(数据传输对象) | BannerV2DTO.java |
要加字段 | 这是定义数据结构的"前端 interface" |
| Controller | BannerV2Controller.java |
不改 | 只依赖接口,不关心字段变化 |
结论
- Java中itf只声明方法,impl是具体实现类
- 前端interface描述数据长什么样(字段),Java中itf描述能调用哪些方法 ,即itf只是对应interface里方法而不包含成员