前言

大家好,距离我上次更新已经过去了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 {
    @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想象成一个高级餐厅的服务模式

// 传统方式:像快餐店,什么都要自己来
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)
@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方法注入依赖
    @Autowired
    public void setPaymentService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
    
    @Autowired
    public void setInventoryService(InventoryService inventoryService) {
        this.inventoryService = inventoryService;
    }
}
3. 字段注入(Field Injection)
@Service
public class OrderService {
    // 字段注入:直接在字段上注入(最常用)
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private 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 {
    @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. 代码简洁性对比

传统方式:

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 {
    @Test
    public void testCreateOrder() {
        // 需要创建所有依赖的真实对象
        UserService userService = new UserService();
        ProductService productService = new ProductService();
        // ... 创建一堆依赖
        
        OrderService orderService = new OrderService();
        // 测试逻辑...
    }
}

Spring方式测试:

@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. 配置的灵活性

// 开发环境配置
@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的具体实现方式,通过注入的方式来提供依赖对象

深入理解:

// 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 {
    @Autowired
    private ServiceB serviceB;
}

@Service  
public class ServiceB {
    @Autowired
    private 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的"魔法"背后是深刻的设计思想和精妙的技术实现:

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

写在结尾

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

Logo

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务

更多推荐