三层架构、IOC 与 DI 学习笔记
一、这一章在讲什么?
这一章主要讲 Spring Web 项目中为什么要把代码拆成 Controller / Service / Dao 三层。原来所有代码都写在 Controller 中,会导致职责混乱,不利于复用、扩展和维护。拆分三层之后,每一层只负责自己的事情,符合单一职责原则。后面进一步引出 IOC 和 DI,用 Spring 容器管理对象、注入依赖,降低类和类之间的耦合。
二、核心概念
1. 单一职责原则
- 它是什么?
一个类或一个方法尽量只负责一类事情。 - 有什么作用?
让代码职责清晰,后期修改时不容易牵一发动全身。 - 原理是什么?
把不同类型的功能拆到不同层或不同类中,例如请求处理、业务处理、数据访问分开写。 - 初学者容易混淆的点
拆分不是为了让代码变多,而是为了让每段代码更清楚、更容易维护。
2. 三层架构
- 它是什么?
把 Web 项目代码分成Controller、Service、Dao三层。 - 有什么作用?
提高代码的扩展性、复用性和可维护性。 - 原理是什么?
请求先进入Controller,Controller调用Service,Service调用Dao获取数据,最后返回结果。 - 初学者容易混淆的点
Controller不负责全部代码,Service不直接等于数据库层,Dao不处理业务逻辑。
3. Controller
- 它是什么?
控制层。 - 有什么作用?
接收前端请求,调用业务层,并响应数据。 - 原理是什么?
通过@RequestMapping等注解接收请求,然后调用Service方法拿到结果。 - 初学者容易混淆的点
Controller不是写所有逻辑的地方,它主要负责“接请求、调业务、回数据”。
4. Service
- 它是什么?
业务逻辑层。 - 有什么作用?
处理具体业务逻辑,例如把字符串数据解析成User对象集合。 - 原理是什么?
Service通常调用Dao获取原始数据,再进行业务处理。 - 初学者容易混淆的点
Service不是单纯读取数据,它更关注业务规则和处理流程。
5. Dao
- 它是什么?
数据访问层,Dao是Data Access Object的缩写。 - 有什么作用?
负责数据访问操作,比如读取文件、查询数据库、增删改查。 - 原理是什么?
把和数据来源有关的代码集中到Dao中,其他层不直接关心数据从哪里来。 - 初学者容易混淆的点
Dao只负责拿数据,不负责复杂业务判断。
6. 高内聚、低耦合
- 它是什么?
高内聚:一个模块内部功能联系紧密。低耦合:模块之间依赖程度低。 - 有什么作用?
让程序更容易扩展和替换实现。 - 原理是什么?
每层只依赖自己需要的接口或对象,不把具体实现写死。 - 初学者容易混淆的点
三层架构只是第一步,如果代码里还直接new具体实现类,耦合仍然比较高。
7. IOC
- 它是什么?
IOC 是Inversion Of Control,叫控制反转。 - 有什么作用?
把对象创建和管理交给 Spring IOC 容器,减少类之间的直接依赖。 - 原理是什么?
原来对象由程序员在类中new出来,现在由容器统一创建、管理和提供。 - 初学者容易混淆的点
IOC 不是不创建对象,而是对象不再由当前类自己创建,而是交给容器创建。
8. Bean
- 它是什么?
被 IOC 容器创建和管理的对象,称为 Bean。 - 有什么作用?
让 Spring 能统一管理对象,并在需要时完成依赖注入。 - 原理是什么?
在类上加声明 Bean 的注解,Spring 扫描到后会创建并放入 IOC 容器。 - 初学者容易混淆的点
不是所有 Java 对象都叫 Bean,只有被 IOC 容器管理的对象才是 Bean。
9. DI
- 它是什么?
DI 是Dependency Injection,叫依赖注入。 - 有什么作用?
当一个类需要另一个对象时,由容器把依赖对象注入进来。 - 原理是什么?
例如Controller需要UserService,Spring 就从容器中找到对应 Bean 并赋值给它。 - 初学者容易混淆的点
DI 不是“控制注入”,而是“依赖注入”。IOC 负责把对象交给容器管理,DI 负责把容器里的对象注入给需要它的类。
10. 声明 Bean 的注解
- 它是什么?
常见有@Component、@Controller、@Service、@Repository。 - 有什么作用?
告诉 Spring:这个类需要交给 IOC 容器管理。 - 原理是什么?
Spring Boot 默认扫描启动类所在包及其子包,被扫描到的 Bean 注解才会生效。 - 初学者容易混淆的点
@Component最通用;@Controller用于控制层;@Service用于业务层;@Repository用于数据访问层。
三、重难点
1. 为什么要拆三层?
结论:为了让代码职责更清楚,提高扩展性和复用性。
原因:如果所有代码都写在 Controller 中,请求处理、业务逻辑、数据访问会混在一起,后期改一个功能可能影响一大片。
比喻:像餐厅分工,前台接待顾客,厨师做菜,采购负责食材。如果一个人全干,事情一多就乱。
2. IOC 中“控制反转”到底反转了什么?
结论:反转的是对象创建和管理的控制权。
原因:以前类中自己 new 对象,现在交给 Spring IOC 容器创建和管理。
比喻:以前你自己买菜做饭,现在把做饭交给食堂,你需要饭时直接去取。
3. IOC 和 DI 的关系
结论:IOC 是思想,DI 是实现这种思想的方式之一。
原因:IOC 让容器管理对象,DI 让容器把对象注入给需要它的类。
例子:UserController 不自己创建 UserServiceImpl,而是通过 @Autowired 让容器注入 UserService。
4. 多个同类型 Bean 为什么会报错?
结论:因为 @Autowired 默认按照类型注入,同类型 Bean 有多个时,Spring 不知道选哪个。
原因:比如 UserService 有 UserServiceImpl 和 UserServiceImpl2 两个实现类,类型都匹配。
比喻:你说“给我一个杯子”,桌上有两个杯子,别人不知道你要哪一个,所以需要你指定。
四、代码理解
1. 三层调用关系
@RestController
public class UserController {
@Autowired
private UserService userService; // 注入业务层对象
@RequestMapping("/list")
public List<User> list() {
// 调用 service 查询用户信息
List<User> userList = userService.list();
return userList; // 响应数据
}
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao; // 注入数据访问层对象
@Override
public List<User> list() {
// 调用 dao 获取原始数据
List<String> lines = userDao.list();
// 把字符串数据解析成 User 对象集合
return lines.stream().map(line -> {
String[] parts = line.split(",");
Integer id = Integer.parseInt(parts[0]);
String username = parts[1];
String password = parts[2];
String name = parts[3];
Integer age = Integer.parseInt(parts[4]);
LocalDateTime updateTime = LocalDateTime.parse(
parts[5],
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
);
return new User(id, username, password, name, age, updateTime);
}).collect(Collectors.toList());
}
}
@Repository
public class UserDaoImpl implements UserDao {
@Override
public List<String> list() {
// 读取 user.txt 文件
InputStream in = this.getClass()
.getClassLoader()
.getResourceAsStream("user.txt");
return IOUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
}
}
语法规则总结:
@RestController:声明控制层 Bean,同时返回数据。@Service:声明业务层 Bean。@Repository:声明数据访问层 Bean。@Autowired:从 IOC 容器中找到需要的 Bean,并注入进来。Controller -> Service -> Dao:请求处理的基本调用顺序。
2. 依赖注入的三种方式
@Autowired
private UserService userService;
属性注入:写法简单,但依赖关系不够明显。
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
构造函数注入:依赖关系清晰,安全性更高。如果只有一个构造函数,@Autowired 可以省略。
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
Setter 注入:依赖关系清晰,但需要多写 setter 方法。
3. 多 Bean 冲突的解决方式
@Primary
@Service
public class UserServiceImpl implements UserService {
}
@Primary:多个同类型 Bean 中,优先选择这个。
@Autowired
@Qualifier("userServiceImpl")
private UserService userService;
@Qualifier:配合 @Autowired,按名称指定 Bean。
@Resource(name = "userServiceImpl")
private UserService userService;
@Resource:按名称指定 Bean。它是 Java EE 提供的注解,不是 Spring 提供的。
五、易错点
- 以为三层架构只是把代码拆开,忘了核心目的是单一职责和降低维护成本。
- 把
Controller当成万能层,在里面写业务逻辑和数据访问代码。 - 把 IOC 理解成“不创建对象”,其实是对象由容器创建和管理。
- 把 DI 说成“控制注入”,正确说法是“依赖注入”。
- 忘记
@Autowired默认按类型注入,同类型 Bean 有多个时会报错。
六、记忆口诀 / 通俗比喻
- Controller 管请求,Service 管业务,Dao 管数据。
- 三层架构治“职责混乱”,IOC / DI 治“对象写死”。
- IOC:对象交给容器管。DI:容器把对象送过来。
@Autowired默认按类型找,多个 Bean 要指定。@Primary定优先级,@Qualifier指名字,@Resource默认按名称。
七、应用
在实际 Spring Boot 项目中,通常会把控制器、业务逻辑、数据访问分开放在不同包中。例如用户列表查询功能:UserController 接收 /list 请求,调用 UserService 获取用户列表,UserServiceImpl 处理业务逻辑,UserDaoImpl 负责读取文件或数据库。
这样做的好处是,如果以后数据来源从 user.txt 换成数据库,通常只需要改 Dao 层;如果业务规则变了,主要改 Service 层;如果接口路径或响应格式变了,主要改 Controller 层。每一层各管各的,项目就更容易维护。
八、最终总结
本章核心是从三层架构理解 Spring 项目的分工:Controller 接收请求并响应数据,Service 处理业务逻辑,Dao 负责数据访问。三层架构体现了单一职责原则,也让代码更容易扩展和复用。IOC 把对象创建和管理交给容器,DI 让容器把依赖对象注入进来。@Autowired 默认按类型注入,如果同类型 Bean 有多个,可以用 @Primary、@Qualifier 或 @Resource 解决。

603

被折叠的 条评论
为什么被折叠?



