当前位置: 首页 > news >正文

从 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的解决方案:

  1. 一级缓存(singletonObjects):存放完全初始化好的单例对象
  2. 二级缓存(earlySingletonObjects):存放原始的bean对象(未填充属性)
  3. 三级缓存(singletonFactories):存放bean工厂对象

总结:IoC和DI的精髓

通过今天的深入学习,我们可以总结出IoC和DI的核心价值:

 核心思想

  • 控制反转:让Spring容器来管理对象,而不是程序员手动管理
  • 依赖注入:让Spring自动注入依赖,而不是硬编码创建

 带来的好处

  1. 代码更简洁:专注业务逻辑,而非基础设施
  2. 耦合度更低:依赖于接口而非具体实现
  3. 可测试性更强:轻松mock依赖进行单元测试
  4. 可维护性更好:配置化管理依赖关系
  5. 可扩展性更强:轻松替换不同的实现

 记住这个类比

把IoC容器想象成一个智能管家

  • 你告诉管家你需要什么(通过注解)
  • 管家帮你准备好一切(对象创建和依赖注入)
  • 你专心做你的事情(编写业务逻辑)

Spring的魔法其实不是魔法

Spring的"魔法"背后是深刻的设计思想和精妙的技术实现:

  • 反射机制:动态创建对象
  • 注解处理:标识需要管理的组件
  • 依赖分析:构建对象间的依赖关系图
  • 生命周期管理:统一管理对象的创建、初始化、销毁

写在结尾

笔者将不定期更新,谢谢大家

http://www.dtcms.com/a/407041.html

相关文章:

  • Spring Validation 校验
  • 好的做彩平图的网站网站的seo 如何优化
  • CTF攻防世界WEB精选基础入门:robots
  • 【网络套接字编程】基于UDP协议的公共聊天室项目
  • python爬虫技术的运用与分析
  • 站长之家查询域名网站建设与维护 电子版
  • 行业商城网站建设多少钱网站建设软件的英文
  • CSS3 核心知识点与实战案例专栏
  • 企业网站建设中存在的问题分析黄冈做网站的公司哪家好
  • 单片机常见的编程语言有哪些?
  • 2.CSS3.(2).html
  • 什么是TCP/UDP/HTTP?
  • 解决银行任务池场景并发问题
  • 济南企业免费建站3d渲染网站建设
  • git实用命令
  • 面相对象程序设计与模块
  • 四川泰龙建设集团公司官方网站网站服务器 要求
  • 主频72Mhz采用高性能的32 位ARM®Cortex®-M0+ 内核的PY32C673芯片
  • Linux 内核编译,打包
  • android网站开发教程建筑行业网站开发
  • 网站建设一般收费广告设计公司图片
  • C++项目:仿muduo库高并发服务器---------LoopThreadPool模块和TcpServer模块的实现
  • S7-200 SMART GET/PUT 指令深度解析:从参数到故障排查(S7 协议的客户端 - 服务器通信)上
  • C++11之异常
  • 网站开发软硬件网站建设应注意什么
  • wordpress全站注明国外代理ip地址 免费
  • LightDM 深度解析:图形登录管理器的机制、TTY 映射与嵌入式调试实战
  • Dlib库 人脸应用实例 疲劳监测
  • 11.2. Linux 防火墙管理
  • VMware+RockyLinux+ikuai+docker+cri-docker+k8s 自用 实践笔记(三)