前言
大家好,距离我上次更新已经过去了3个多月,很抱歉拖更了三个月. 这几个月我因为一些琐事缺少更新的动力, 但是我并没有停止学习, 我依然花时间在锻炼自己的技术栈, 这次更新希望还有读者能支持我
这次我将接着讨论 IoC 和 DI,希望对大家有帮助.
上篇博客我们讨论了一些传统开发的痛点和Spring框架带来的革命性变化------从繁琐的手动对象创建到优雅的自动依赖注入。相信你已经对Spring的魅力有了初步的认识。
今天,我们将揭开Spring"魔法"背后的神秘面纱,深入探讨IoC(控制反转)和DI(依赖注入)这两个核心概念。理解了它们,你就真正掌握了Spring的灵魂!
本博客参考了 小林coding 和一些大佬的博客, 附上我自己的思考.
什么是IoC(控制反转)?
从"买菜做饭"说起
想象一下你要做一顿丰盛的晚餐:
传统方式(主动控制):
java
// 就像你亲自买菜做饭
public class Chef {
private Vegetable vegetable;
private Meat meat;
private Rice rice;
public Chef() {
// 自己去买菜(创建依赖)
this.vegetable = new Vegetable("白菜");
this.meat = new Meat("猪肉");
this.rice = new Rice("大米");
}
public void cookDinner() {
// 自己做饭
System.out.println("用" + vegetable.getName() + "、" +
meat.getName() + "、" + rice.getName() + "做晚餐");
}
}
IoC方式(被动接收):
java
// 就像有人把食材直接送到你手上
@Component
public class Chef {
@Autowired
private Vegetable vegetable; // 有人帮你准备好
@Autowired
private Meat meat;
@Autowired
private Rice rice;
public void cookDinner() {
// 专心做饭就行了
System.out.println("用" + vegetable.getName() + "、" +
meat.getName() + "、" + rice.getName() + "做晚餐");
}
}
IoC的本质理解
控制反转的"控制"指的是什么?
- 对象的创建控制权
- 对象的生命周期管理控制权
- 对象之间的依赖关系控制权
"反转"又体现在哪里?
传统方式 | IoC方式 |
---|---|
对象主动创建依赖 | 容器主动注入依赖 |
程序员控制对象生命周期 | Spring容器控制对象生命周期 |
硬编码依赖关系 | 配置化依赖关系 |
一个生动的类比
把IoC想象成一个高级餐厅的服务模式:
java
// 传统方式:像快餐店,什么都要自己来
public class FastFoodCustomer {
public void eat() {
// 自己排队点餐
Food food = new Hamburger();
// 自己找座位
Seat seat = new Seat();
// 自己倒水
Drink drink = new Coke();
// 终于可以吃了...
}
}
// IoC方式:像高级餐厅,专人服务
@Component
public class RestaurantCustomer {
@Autowired
private Food food; // 服务员帮你点餐
@Autowired
private Seat seat; // 服务员安排座位
@Autowired
private Drink drink; // 服务员倒水
public void eat() {
// 专心享用美食就行了!
}
}
什么是DI(依赖注入)?
DI是IoC的具体实现
如果说IoC是一种设计思想,那么DI就是这种思想的具体实现方式。
依赖注入的三种方式:
1. 构造器注入(Constructor Injection)
java
@Service
public class OrderService {
private final PaymentService paymentService;
private final InventoryService inventoryService;
// 构造器注入:在对象创建时就注入依赖
public OrderService(PaymentService paymentService,
InventoryService inventoryService) {
this.paymentService = paymentService;
this.inventoryService = inventoryService;
}
}
优点:
- 保证依赖不为null
- 支持final字段
- 便于单元测试
2. Setter注入(Setter Injection)
java
@Service
public class OrderService {
private PaymentService paymentService;
private InventoryService inventoryService;
// Setter注入:通过setter方法注入依赖
@Autowired
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
@Autowired
public void setInventoryService(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}
}
3. 字段注入(Field Injection)
java
@Service
public class OrderService {
// 字段注入:直接在字段上注入(最常用)
@Autowired
private PaymentService paymentService;
@Autowired
private InventoryService inventoryService;
}
DI的工作原理图解
┌─────────────────────────────────────────────────┐
│ Spring容器 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │OrderService │ │PaymentService│ │
│ │ │ │ │ │
│ └─────────────┘ └─────────────┘ │
│ │ │ │
│ └─────── 自动注入 ────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │UserService │ │EmailService │ │
│ │ │ │ │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────┘
深入理解:Spring容器是如何工作的?
Spring容器的启动过程
java
// 模拟Spring容器的简化版本
public class SimpleSpringContainer {
private Map<String, Object> beans = new HashMap<>();
public void start() {
// 1. 扫描所有带@Component注解的类
scanComponents();
// 2. 创建对象实例
createInstances();
// 3. 注入依赖关系
injectDependencies();
// 4. 初始化回调
callInitMethods();
}
private void scanComponents() {
// 扫描classpath下的所有类
// 找到带有@Service, @Component等注解的类
}
private void createInstances() {
// 使用反射创建对象实例
// Class.forName().newInstance()
}
private void injectDependencies() {
// 分析依赖关系
// 将依赖的对象注入到目标对象中
}
}
一个完整的实战例子
让我们用一个完整的电商系统来演示IoC和DI的威力:
java
// 用户服务
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User findUser(Long userId) {
return userRepository.findById(userId);
}
}
// 商品服务
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public Product findProduct(Long productId) {
return productRepository.findById(productId);
}
}
// 库存服务
@Service
public class InventoryService {
public boolean checkStock(Long productId, int quantity) {
// 检查库存逻辑
return true;
}
public void deductStock(Long productId, int quantity) {
System.out.println("扣减商品" + productId + "库存" + quantity);
}
}
// 支付服务
@Service
public class PaymentService {
public void processPayment(Order order) {
System.out.println("处理订单支付:" + order.getTotalAmount());
}
}
// 邮件服务
@Service
public class EmailService {
public void sendOrderConfirmation(Order order) {
System.out.println("发送订单确认邮件给:" + order.getUserEmail());
}
}
// 核心的订单服务
@Service
public class OrderService {
// 看!多么简洁!没有任何对象创建代码
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@Autowired
private EmailService emailService;
public void createOrder(Long userId, Long productId, int quantity) {
// 1. 验证用户
User user = userService.findUser(userId);
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 2. 验证商品
Product product = productService.findProduct(productId);
if (product == null) {
throw new RuntimeException("商品不存在");
}
// 3. 检查库存
if (!inventoryService.checkStock(productId, quantity)) {
throw new RuntimeException("库存不足");
}
// 4. 创建订单
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setTotalAmount(product.getPrice() * quantity);
order.setUserEmail(user.getEmail());
// 5. 扣减库存
inventoryService.deductStock(productId, quantity);
// 6. 处理支付
paymentService.processPayment(order);
// 7. 发送确认邮件
emailService.sendOrderConfirmation(order);
System.out.println("订单创建成功!订单号:" + order.getOrderId());
}
}
// 启动类
@SpringBootApplication
public class ECommerceApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(ECommerceApplication.class, args);
// 从容器中获取OrderService
OrderService orderService = context.getBean(OrderService.class);
// 执行业务逻辑
orderService.createOrder(1L, 100L, 2);
}
}
IoC和DI带来的巨大优势
1. 代码简洁性对比
传统方式:
java
public class OrderService {
public OrderService() {
// 20行对象创建代码
this.userService = new UserService();
this.productService = new ProductService();
// ... 更多依赖创建
}
public void createOrder() {
// 5行业务逻辑
}
}
Spring方式:
java
@Service
public class OrderService {
@Autowired private UserService userService;
@Autowired private ProductService productService;
public void createOrder() {
// 5行业务逻辑(专注核心)
}
}
2. 可测试性飞跃
传统方式测试:
java
// 测试代码比业务代码还复杂
public class OrderServiceTest {
@Test
public void testCreateOrder() {
// 需要创建所有依赖的真实对象
UserService userService = new UserService();
ProductService productService = new ProductService();
// ... 创建一堆依赖
OrderService orderService = new OrderService();
// 测试逻辑...
}
}
Spring方式测试:
java
@ExtendWith(SpringExtension.class)
class OrderServiceTest {
@Mock
private UserService userService;
@Mock
private ProductService productService;
@InjectMocks
private OrderService orderService;
@Test
void testCreateOrder() {
// 轻松mock,专注测试逻辑
when(userService.findUser(1L)).thenReturn(mockUser);
when(productService.findProduct(100L)).thenReturn(mockProduct);
orderService.createOrder(1L, 100L, 2);
verify(userService).findUser(1L);
verify(productService).findProduct(100L);
}
}
3. 配置的灵活性
java
// 开发环境配置
@Configuration
@Profile("dev")
public class DevConfig {
@Bean
public PaymentService paymentService() {
return new MockPaymentService(); // 使用模拟支付
}
}
// 生产环境配置
@Configuration
@Profile("prod")
public class ProdConfig {
@Bean
public PaymentService paymentService() {
return new RealPaymentService(); // 使用真实支付
}
}
常见面试题深度解析
Q1: IoC和DI的区别是什么?
标准答案:
- **IoC(控制反转)**是一种设计思想,强调将对象的创建和管理权交给外部容器
- **DI(依赖注入)**是IoC的具体实现方式,通过注入的方式来提供依赖对象
深入理解:
java
// IoC思想:我不创建依赖,由容器提供
public interface PaymentService {
void pay(Order order);
}
// DI实现:具体怎么注入依赖
@Service
public class OrderService {
@Autowired // DI的具体实现方式
private PaymentService paymentService;
}
Q2: Spring是怎么解决循环依赖的?
这是一个高频面试题,涉及Spring的三级缓存机制:
java
// 假设A依赖B,B依赖A
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
Spring的解决方案:
- 一级缓存(singletonObjects):存放完全初始化好的单例对象
- 二级缓存(earlySingletonObjects):存放原始的bean对象(未填充属性)
- 三级缓存(singletonFactories):存放bean工厂对象
总结:IoC和DI的精髓
通过今天的深入学习,我们可以总结出IoC和DI的核心价值:
核心思想
- 控制反转:让Spring容器来管理对象,而不是程序员手动管理
- 依赖注入:让Spring自动注入依赖,而不是硬编码创建
带来的好处
- 代码更简洁:专注业务逻辑,而非基础设施
- 耦合度更低:依赖于接口而非具体实现
- 可测试性更强:轻松mock依赖进行单元测试
- 可维护性更好:配置化管理依赖关系
- 可扩展性更强:轻松替换不同的实现
记住这个类比
把IoC容器想象成一个智能管家:
- 你告诉管家你需要什么(通过注解)
- 管家帮你准备好一切(对象创建和依赖注入)
- 你专心做你的事情(编写业务逻辑)
Spring的魔法其实不是魔法
Spring的"魔法"背后是深刻的设计思想和精妙的技术实现:
- 反射机制:动态创建对象
- 注解处理:标识需要管理的组件
- 依赖分析:构建对象间的依赖关系图
- 生命周期管理:统一管理对象的创建、初始化、销毁
写在结尾
笔者将不定期更新,谢谢大家