前言
学习苍穹外卖第 2 天。今天不满足于 "代码能跑就行",我决定彻底搞清楚 Spring 最核心的两个概念:IoC(控制反转) 和 DI(依赖注入)。然后用一个真实的接口,把 Controller → Service → Mapper 的完整调用链走一遍。
一、传统方式:我自己招人干活
想象一下,没有 Spring 框架,我要开一家餐厅。我是老板(Service),需要一个切菜工(Mapper)来干活。
我只能自己去招人:
public class EmployeeServiceImpl {
// 我自己负责"找"和"创建"这个切菜工
private EmployeeMapper employeeMapper = new EmployeeMapperImpl();
public void login() {
employeeMapper.getByUsername(…); // 然后才能用他
}
}
这么做的问题:
- 强依赖:必须精确知道去哪找切菜工(EmployeeMapperImpl),实现类变更,代码必须修改
- 职责不清:既要写业务逻辑,又要负责对象创建,关注点混乱
- 浪费资源:多个 Service 依赖同一个 Mapper,需重复创建对象,无法复用
二、Spring 方式:中介公司包办一切
Spring 框架开了一家强大的 "中介公司",学名叫做 IoC 容器(控制反转容器)。
核心概念
- 控制反转(IoC):把 "创建对象、管理对象" 的控制权,从开发者代码交给 Spring 容器
- 依赖注入(DI):组件需要的依赖对象,由容器自动装配进来,无需手动 new
所有 Bean(@Service/@Mapper/@Controller 等)在程序启动时被容器创建、管理;需要依赖时,容器自动注入,实现"别找我,我会找你"的设计哲学。
三、代码里的体现
@Service // 1. 注册为Bean,交给Spring管理
public class EmployeeServiceImpl {
@Autowired // 2. 声明依赖,容器自动注入
private EmployeeMapper employeeMapper; // 3. 无需手动创建,直接使用
public void login() {
employeeMapper.getByUsername(…); // 4. 直接调用
}
}
流程拆解
四、为什么要用 IoC/DI?
核心价值:解耦
- 只依赖接口,不依赖具体实现类
- 替换实现类时,业务代码无需修改
- 统一管理对象生命周期,支持单例 / 多例等策略
一句话总结
- 控制反转(IoC):交出对象创建与管理的控制权
- 依赖注入(DI):需要的依赖,框架自动送过来
五、实战:启用 / 禁用员工完整调用链
以启用 / 禁用员工功能为例,打通三层调用全流程。
需求
管理员点击禁用按钮,将员工账号状态改为停用(0)。
- 前端请求:POST /admin/employee/status/0?id=3
- 含义:ID=3 的员工状态设为 0(禁用)
第一棒:Controller 层(接待员)
负责接收请求、参数解析、调用 Service、返回统一结果
// EmployeeController.java
@PostMapping("/status/{status}")
@ApiOperation("启用禁用员工账号")
public Result startOrStop(@PathVariable Integer status, Long id) {
log.info("启用禁用员工账号:{},{}", status, id);
employeeService.startOrStop(status, id);
return Result.success();
}
- 解析 URL 路径参数status与请求参数id
- 不处理业务,直接转发给 Service
- 封装 Result 返回前端
第二棒:Service 层(业务处理层)
负责业务逻辑编排、数据组装
// EmployeeServiceImpl.java
@Override
public void startOrStop(Integer status, Long id) {
// 组装要更新的数据
Employee employee = Employee.builder()
.status(status)
.id(id)
.updateTime(LocalDateTime.now())
.updateUser(BaseContext.getCurrentId())
.build();
// 交给Mapper执行
employeeMapper.update(employee);
}
踩坑记录
// 错误写法:build()返回新对象未接收,传入空对象
Employee employee = new Employee();
Employee.builder().status(status).id(id).build();
employeeMapper.update(employee); // SQL条件id=null
✅ 正确写法:用变量接收builder().build()返回的对象
第三棒:Mapper 层(数据访问层)
Mapper 接口
// EmployeeMapper.java
@Mapper
public interface EmployeeMapper {
void update(Employee employee);
}
XML SQL 映射
EmployeeMapper.xml –>
<update id="update" parameterType="Employee">
update employee
>
="name != null">name = #{name},>
="username != null">username = #{username},>
="password != null">password = #{password},>
="phone != null">phone = #{phone},>
="sex != null">sex = #{sex},>
="idNumber != null">id_number = #{idNumber}, != null">update_time = #{updateTime},>
="updateUser != null">update_user = #{updateUser}, null">status = #{status}, id = #{id}
</update>
MyBatis 执行流程
最终执行 SQL:
update employee set status = 0, update_time = '2026-08-01…', update_user = 1 where id = 3
结果返回链路
数据库更新成功
→ Mapper 返回 void
→ Service 返回 void
→ Controller 封装 Result.success()
→ 前端收到 {"code": 1, "msg": "操作成功"}
六、完整调用链路图
前端 HTTP 请求
↓
Controller 层(接收请求、参数解析)
| 数据形态:零散参数 → 传给Service
↓
Service 层(业务编排、对象组装)
| 数据形态:零散参数 → Employee实体
↓
Mapper 层(SQL生成、数据操作)
| 数据形态:实体 → 执行SQL
↓
数据库执行
↓
结果层层返回 → Controller封装 → 前端响应
结尾
今天终于搞懂了IoC/DI!看课的时候一直云里雾里感觉跟着敲完啥都没学到;打通了Controller→Service→Mapper三层调用全流程,终于是觉得学到东西了。
如果你也在学 Spring,希望这篇博客能帮你把 IoC、DI 和三层调用链彻底吃透~ 欢迎评论区交流,一起进步!
网硕互联帮助中心

评论前必须登录!
注册