IoC 与 DI:Spring 的灵魂¶
这是读懂 Spring 的第一把钥匙。两个词:IoC(控制反转)和 DI(依赖注入)。
没有 Spring 的世界(手动 new)¶
假设 TaskController 要用 TaskService,传统写法是自己 new:
问题:Controller 和具体实现 TaskServiceImpl 死死绑死了。想换成别的实现?想写单元测试替换成假的?都很难。
Spring 的做法:别自己 new,让容器给你¶
@RestController
public class TaskController {
private final TaskService taskService;
public TaskController(TaskService taskService) { // 构造器声明"我需要 TaskService"
this.taskService = taskService;
}
}
Controller 只声明"我需要 TaskService",不关心它怎么来。Spring 启动时发现 @RestController、@Service 这些注解(第 18 章讲的反射),自动:
- 创建
TaskServiceImpl实例(因为标了@Service); - 创建
TaskController实例(因为标了@RestController); - 把
TaskServiceImpl塞进TaskController的构造器。
flowchart LR
A["@Service TaskServiceImpl\n(你只贴注解)"] --> C["Spring 容器:自动 new、自动装配"]
B["@RestController TaskController\n(只声明需要 TaskService)"] --> C
C --> D["运行时:Controller 拿到 Service,直接能用"]
控制反转:对象的创建/管理"控制权"从你的代码反转给了 Spring 容器。 依赖注入:容器把依赖注入到需要它的对象里。
对标前端¶
- 你写 Vue 组件时
props从父组件传进来,组件自己不 new 数据——这就是"注入"的思想。 - Spring 把这种"谁来提供依赖"的工作,交给了一个统一的容器,全局自动管理。
三种注入方式¶
// 1. 构造器注入(推荐,本书统一用这个)
public TaskController(TaskService taskService) { ... }
// 2. 字段注入(@Autowired,简单但不推荐)
@Autowired private TaskService taskService;
// 3. setter 注入(少用)
为什么推荐构造器注入
依赖一目了然(看构造器参数就知道这玩意需要啥)、字段能 final(不可变更安全)、方便单元测试(直接 new 传参)。IDEA 也会提示你用构造器注入。
实际代码¶
下面是任务接口 Controller,它的 TaskService 就是通过构造器注入的——它自己没有 new:
package com.javaglm.task.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.javaglm.task.common.Result;
import com.javaglm.task.dto.TaskRequest;
import com.javaglm.task.entity.Task;
import com.javaglm.task.service.TaskService;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
/**
* 任务接口(第 28 章):增删改查 + 分页。
* 注意 userId 来自 JWT 拦截器放进请求上下文的值(不是前端传的,防伪造)。
*/
@RestController
@RequestMapping("/tasks")
public class TaskController {
private final TaskService taskService;
public TaskController(TaskService taskService) {
this.taskService = taskService;
}
@GetMapping
public Result<IPage<Task>> list(
@RequestParam(defaultValue = "1") long current,
@RequestParam(defaultValue = "10") long size,
@RequestParam(required = false) Integer status,
HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return Result.success(taskService.pageTasks(current, size, userId, status));
}
@PostMapping
public Result<Task> create(@RequestBody @Valid TaskRequest req,
HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return Result.success(taskService.createTask(req, userId));
}
@PutMapping("/{id}")
public Result<Task> update(@PathVariable Long id,
@RequestBody @Valid TaskRequest req,
HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return Result.success(taskService.updateTask(id, req, userId));
}
@DeleteMapping("/{id}")
public Result<?> delete(@PathVariable Long id, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
taskService.deleteTask(id, userId);
return Result.success();
}
}
认证接口同理:
package com.javaglm.task.controller;
import com.javaglm.task.common.Result;
import com.javaglm.task.dto.LoginRequest;
import com.javaglm.task.dto.RegisterRequest;
import com.javaglm.task.service.AuthService;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Map;
/**
* 认证接口(第 29 章):注册 / 登录。
* 这两个路径在 WebMvcConfig 里被 JWT 拦截器放行(不用登录就能访问)。
*/
@RestController
@RequestMapping("/auth")
public class AuthController {
private final AuthService authService;
public AuthController(AuthService authService) {
this.authService = authService;
}
@PostMapping("/register")
public Result<?> register(@RequestBody @Valid RegisterRequest req) {
authService.register(req);
return Result.success();
}
@PostMapping("/login")
public Result<Map<String, Object>> login(@RequestBody @Valid LoginRequest req) {
return Result.success(authService.login(req));
}
}
:octicons-arrow-left-16: 上一章:多线程入门 | 下一章:第一个 Spring Boot 项目