一、Spring Framework 概述
1. 为什么需要 Spring?
| 问题 | 传统的解决方案 | Spring 的解决方案 |
|---|---|---|
| 对象创建混乱 | 手动 new 对象 | 容器统一管理 |
| 依赖关系复杂 | 硬编码依赖 | 依赖注入 |
| 代码重复 | 相同逻辑重复编写 | AOP 面向切面 |
| 资源管理困难 | 手动管理数据库连接 | 自动资源管理 |
2. Spring 的核心价值
| 价值 | 说明 |
|---|---|
| 解耦 | 降低组件间的耦合度 |
| 开发效率 | 减少样板代码 |
| 可测试性 | 便于单元测试 |
| 扩展性 | 易于扩展和维护 |
二、IoC(控制反转)详解
1. 什么是控制反转?
传统方式:
public class OrderService {
// 主动创建依赖对象
private ProductService productService = new ProductService();
private PaymentService paymentService = new PaymentService();
}
Spring 方式:
@Service
public class OrderService {
// 声明需要什么依赖,由 Spring 提供
private final ProductService productService;
private final PaymentService paymentService;
public OrderService(ProductService productService,
PaymentService paymentService) {
this.productService = productService;
this.paymentService = paymentService;
}
}
2. 控制反转的三个关键点
| 关键点 | 传统方式 | Spring 方式 |
|---|---|---|
| 对象创建 | 开发者手动 new | Spring 容器创建 |
| 依赖管理 | 开发者主动查找 | Spring 容器注入 |
| 生命周期 | 开发者手动管理 | Spring 容器管理 |
3. IoC 容器
IoC 容器是 Spring 的核心,它负责:
- 实例化对象:根据配置创建对象实例
- 配置对象:设置对象的属性和依赖关系
- 组装对象:将对象之间的依赖关系注入
- 管理生命周期:控制对象的创建、初始化、销毁
三、DI(依赖注入)详解
1. 什么是依赖注入?
定义: 依赖注入是实现控制反转的一种方式,通过将对象的依赖关系从内部转移到外部,由容器在运行时动态注入。
示例:
// 被依赖的对象
@Repository
public class UserRepository {
public User findById(Long id) {
// 查询逻辑
return new User();
}
}
// 需要依赖的对象
@Service
public class UserService {
private final UserRepository userRepository;
// 构造器注入
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUser(Long id) {
return userRepository.findById(id);
}
}
2. 三种依赖注入方式
方式一:构造器注入(推荐)
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// 构造器注入
public UserService(UserRepository userRepository,
EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
}
优点:
- ✅ 确保依赖不可变(final)
- ✅ 确保依赖非空
- ✅ 清晰表达依赖关系
- ✅ 便于单元测试(不需要反射)
方式二:Setter 注入
@Service
public class UserService {
private UserRepository userRepository;
private EmailService emailService;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
适用场景:
- 可选依赖
- 可能会变化的依赖
方式三:字段注入(不推荐)
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EmailService emailService;
}
缺点:
- ❌ 依赖可能为空
- ❌ 无法使用 final
- ❌ 难以单元测试
- ❌ 违反单一职责原则
3. 依赖注入的好处
| 好处 | 说明 |
|---|---|
| 解耦 | 降低组件间的耦合度 |
| 可测试性 | 容易注入 Mock 对象 |
| 灵活性 | 依赖可以动态替换 |
| 代码简洁 | 减少手动管理依赖的代码 |
四、Bean 的管理
1. 什么是 Bean?
Bean 是 Spring 容器管理的对象,也称为 Spring Bean。
Bean 的生命周期:
实例化 → 属性赋值 → 初始化 → 使用 → 销毁
2. Bean 的注解
| 注解 | 作用 | 使用场景 |
|---|---|---|
@Component | 通用组件注解 | 任何需要 Spring 管理的类 |
@Service | 服务层注解 | 业务逻辑层 |
@Repository | 数据访问层注解 | 数据库访问、DAO |
@Controller | 控制器注解 | Web 层、处理 HTTP 请求 |
@Configuration | 配置类注解 | Spring 配置类 |
注意: 这些注解本质上是 @Component 的特化版本。
3. Bean 的注册方式
方式一:注解扫描(推荐)
@Service
public class UserService {
// Spring 自动扫描并注册
}
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
}
方式二:Java 配置类
@Configuration
public class AppConfig {
@Bean
public UserService userService(UserRepository userRepository) {
return new UserService(userRepository);
}
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
}
4. Bean 的作用域
| 作用域 | 说明 | 使用场景 |
|---|---|---|
| singleton(默认) | 单例,整个容器只有一个实例 | 无状态的服务 |
| prototype | 原型,每次请求创建新实例 | 有状态的对象 |
| request | 每次 HTTP 请求创建一个实例 | Web 应用中的请求作用域 |
| session | 每个 HTTP Session 创建一个实例 | Web 应用中的会话作用域 |
示例:
@Service
@Scope("prototype") // 原型作用域
public class PrototypeService {
// 每次注入都会创建新实例
}
五、依赖注入的高级特性
1. 循环依赖
什么是循环依赖?
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
循环依赖的问题:
- 违反了良好的设计原则
- 代码耦合度高
- 难以测试和维护
解决方案:
- 重构代码(推荐):提取公共依赖,消除循环
- 使用 @Lazy:延迟初始化
- 使用 Setter 注入:允许循环依赖
2. @Qualifier 注解
作用: 当有多个相同类型的 Bean 时,指定使用哪一个。
@Configuration
public class AppConfig {
@Bean("primaryEmailService")
public EmailService primaryEmailService() {
return new PrimaryEmailService();
}
@Bean("backupEmailService")
public EmailService backupEmailService() {
return new BackupEmailService();
}
}
@Service
public class UserService {
@Autowired
@Qualifier("primaryEmailService") // 指定使用哪个 Bean
private EmailService emailService;
}
3. @Primary 注解
作用: 指定当有多个相同类型的 Bean 时,默认使用哪一个。
@Configuration
public class AppConfig {
@Primary // 默认使用这个 Bean
@Bean
public EmailService primaryEmailService() {
return new PrimaryEmailService();
}
@Bean
public EmailService backupEmailService() {
return new BackupEmailService();
}
}
4. @Autowired 的可选性
@Service
public class UserService {
@Autowired(required = false) // 如果没有匹配的 Bean,不报错
private OptionalEmailService optionalEmailService;
}
六、最佳实践
1. 依赖注入最佳实践
| 实践 | 说明 |
|---|---|
| 使用构造器注入 | 优先使用构造器注入 |
| 使用 final 字段 | 确保依赖不可变 |
| 避免循环依赖 | 通过重构消除循环依赖 |
| 明确依赖关系 | 通过构造器参数清晰表达依赖 |
2. Bean 命名规范
| 类型 | 命名规范 | 示例 |
|---|---|---|
| Service | XxxService | UserService |
| Repository | XxxRepository | UserRepository |
| Controller | XxxController | UserController |
| Component | XxxComponent | EmailComponent |
3. 避免的反模式
| 反模式 | 问题 | 解决方案 |
|---|---|---|
| 字段注入 | 难以测试、依赖可能为空 | 使用构造器注入 |
| 过度依赖 | 一个类依赖太多其他类 | 拆分职责、使用门面模式 |
| 循环依赖 | 设计问题 | 重构代码、提取公共依赖 |
| 滥用 @Autowired | 让代码难以阅读 | 优先使用构造器注入 |
七、实战示例
1. 完整的依赖注入示例
UserRepository.java:
package com.example.myapp.repository;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public User findById(Long id) {
// 模拟数据库查询
return new User(id, "John Doe", "john@example.com");
}
public User save(User user) {
// 模拟保存逻辑
return user;
}
}
EmailService.java:
package com.example.myapp.service;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
public void sendEmail(String to, String subject, String body) {
System.out.println("发送邮件到: " + to);
System.out.println("主题: " + subject);
System.out.println("内容: " + body);
}
}
UserService.java:
package com.example.myapp.service;
import com.example.myapp.repository.UserRepository;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// 构造器注入(推荐)
public UserService(UserRepository userRepository,
EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
public User getUser(Long id) {
return userRepository.findById(id);
}
public User register(User user) {
User savedUser = userRepository.save(user);
emailService.sendEmail(user.getEmail(),
"欢迎注册",
"欢迎加入我们的平台!");
return savedUser;
}
}
UserController.java:
package com.example.myapp.controller;
import com.example.myapp.model.User;
import com.example.myapp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUser(id);
}
@PostMapping
public User register(@RequestBody User user) {
return userService.register(user);
}
}
八、总结
| 概念 | 核心要点 |
|---|---|
| IoC(控制反转) | 将对象创建和依赖管理的控制权从开发者转移到 Spring 容器 |
| DI(依赖注入) | Spring 容器在运行时动态地将依赖对象注入到目标对象中 |
| Bean | Spring 容器管理的对象 |
| Bean 的作用域 | singleton(默认)、prototype、request、session |
| 依赖注入方式 | 构造器注入(推荐)、Setter 注入、字段注入(不推荐) |
| 高级特性 | 循环依赖、@Qualifier、@Primary、@Autowired 可选性 |
总结问题
1. 什么是 IoC 和 DI?有什么区别?
答案:
- IoC(控制反转):一种设计思想,将对象创建和依赖管理的控制权从开发者转移到容器
- DI(依赖注入):实现 IoC 的一种方式,通过注入依赖来实现控制反转
- 区别:IoC 是思想,DI 是实现方式
2. Spring 有哪些依赖注入方式?推荐使用哪种?
答案:
- 三种方式:构造器注入、Setter 注入、字段注入
- 推荐使用构造器注入
- 原因:确保依赖不可变、非空、清晰表达依赖关系、便于测试
3. 什么是 Bean?Bean 的作用域有哪些?
答案:
- Bean 是 Spring 容器管理的对象
- 作用域:singleton(单例)、prototype(原型)、request、session
4. 什么是循环依赖?如何解决?
答案:
- 循环依赖:两个或多个 Bean 之间相互依赖
- 解决方案:
- 重构代码,消除循环依赖(推荐)
- 使用 @Lazy 延迟初始化
- 使用 Setter 注入