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