【Spring】超详细!一篇文章让你完全理解Spring Ioc和 DI

【Spring】超详细!一篇文章让你完全理解 Spring IoC 和 DI

大家好,我是 Grok。今天我们来聊聊 Spring 框架的核心灵魂——IoC(Inversion of Control,控制反转)DI(Dependency Injection,依赖注入)。如果你是 Spring 小白,或者想彻底搞懂这两个概念,这篇文章绝对够用!我们从基础原理入手,逐步深入到实际代码、配置和高级玩法,保证逻辑清晰、易记。

为什么说“超详细”?因为我会用:

  • 通俗比喻:像“厨师 vs 餐厅老板”来解释抽象概念。
  • 表格对比:快速区分传统方式 vs Spring 方式。
  • 代码示例:Maven + Java 简单项目,复制就能跑。
  • 步骤拆解:从 XML 配置到注解、Java 配置,全覆盖。
  • 常见坑:避免初学者踩雷。

假设你有 Java 基础(类、接口、Maven),没问题就开始吧~

第一部分:基础概念——IoC 和 DI 到底是什么?

1.1 IoC:控制反转(Inversion of Control)

传统编程:你(程序员)像个“厨师”,自己控制一切——创建对象、调用方法、管理依赖。比如,你要一个“汽车”对象,就自己 new Car(),然后自己组装轮子、引擎。

IoC 的核心思想:把“控制权”反转给别人(Spring 容器)。你不再自己 new 对象,而是告诉 Spring:“我需要一个汽车”,Spring 像“餐厅老板”一样,自动帮你组装好、注入依赖、甚至管理生命周期。

  • 为什么需要 IoC?
    传统方式耦合太紧:改一个类,就得改一堆地方。IoC 解耦,让代码更灵活、可测试、可扩展。
  • IoC 的好处(3 大关键词):
  • 解耦:模块间不直接依赖,改动不连锁反应。
  • 复用:对象像“乐高积木”,随时组装。
  • 管理:Spring 统一管理对象创建、销毁、作用域。

IoC 是“思想”,DI 是“实现方式”。IoC 就像“外卖平台”,DI 就像“骑手送餐”。

1.2 DI:依赖注入(Dependency Injection)

DI 是 IoC 的具体实现:Spring 不是让你自己找依赖,而是“注入”给你。

  • 依赖什么? 一个对象需要另一个对象才能工作。比如,Car 需要 EngineWheel
  • 注入怎么做? Spring 通过配置(XML/注解/Java)自动把依赖“塞”进你的对象。

DI 的 3 种方式(按注入时机分):

方式描述优点/缺点示例场景
构造函数注入通过构造方法注入依赖强制依赖(对象创建时就注入),测试友好核心依赖(如数据库连接)
Setter 注入通过 Setter 方法注入灵活(可选依赖),但可能注入不全可选配置(如日志级别)
接口注入通过接口方法注入(较少用)动态,但复杂遗留系统集成

DI 让你的代码从“主动拉取”依赖变成“被动接收”。

1.3 IoC vs DI 的关系(别混淆!)

  • IoC 是大概念:控制反转,包括 DI 和其他(如 AOP)。
  • DI 是 IoC 的子集:具体怎么反转控制?通过注入依赖。
  • Spring 的 IoC 容器:ApplicationContextBeanFactory,管理所有 Bean(对象)。

一句话:IoC 是“老板指挥”,DI 是“老板给你发工具”。

第二部分:Spring 中的 IoC 和 DI 实战

Spring 版本:假设用 Spring Boot 2.7+ 或 Spring Framework 6.x(2026 年主流)。先建个 Maven 项目:

<!-- pom.xml 核心依赖 -->
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.0</version> <!-- 最新版自行替换 -->
    </dependency>
</dependencies>

2.1 配置方式 1:XML 配置(经典,但老派)

XML 是 Spring 最早的配置方式,适合理解底层。

步骤拆解

  1. 定义 Bean:XML 文件中声明对象。
  2. 注入依赖:用 <property><constructor-arg>
  3. 获取容器:ClassPathXmlApplicationContext 加载 XML。

示例代码

  • 接口和类:
public interface Engine { void start(); }

public class PetrolEngine implements Engine {
    public void start() { System.out.println("Petrol Engine started"); }
}

public class Car {
    private Engine engine; // 依赖

    // Setter 注入
    public void setEngine(Engine engine) { this.engine = engine; }

    public void drive() { engine.start(); System.out.println("Car is driving"); }
}
  • XML 配置(applicationContext.xml):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定义 Bean -->
    <bean id="petrolEngine" class="com.example.PetrolEngine" />

    <bean id="car" class="com.example.Car">
        <!-- DI:Setter 注入 -->
        <property name="engine" ref="petrolEngine" />
    </bean>
</beans>
  • 测试:
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Car car = (Car) context.getBean("car"); // IoC 容器提供
        car.drive(); // 输出: Petrol Engine started \n Car is driving
    }
}

构造函数注入版 XML(替换 <property>):

<bean id="car" class="com.example.Car">
    <constructor-arg ref="petrolEngine" />
</bean>

Car 类加构造:public Car(Engine engine) { this.engine = engine; }

2.2 配置方式 2:注解配置(现代主流,简单)

从 Spring 2.5 开始,注解取代 XML。Spring Boot 默认用这个。

关键注解

注解作用示例
@Component标记类为 Bean(通用)@Component public class Car {}
@Service业务层 Bean@Service
@RepositoryDAO 层 Bean@Repository
@ControllerWeb 层 Bean@Controller
@Autowired自动注入依赖(byType,默认)@Autowired private Engine engine;
@Qualifier指定 Bean 名(当多个同类型时)@Qualifier(“petrol”)
@Configuration配置类(代替 XML)@Configuration public class AppConfig {}
@Bean方法返回 Bean@Bean public Engine engine() {}
@ComponentScan扫描包,自动发现 Bean@ComponentScan(“com.example”)

示例代码(注解版):

  • 类:
@Component
public class PetrolEngine implements Engine {
    public void start() { System.out.println("Petrol Engine started"); }
}

@Component
public class Car {
    @Autowired
    private Engine engine; // DI 注入

    public void drive() { engine.start(); System.out.println("Car is driving"); }
}
  • 配置类(可选,如果不扫描默认包):
@Configuration
@ComponentScan("com.example") // 扫描包
public class AppConfig {}
  • 测试:
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        Car car = context.getBean(Car.class);
        car.drive();
    }
}

自动装配模式(@Autowired 的底层):

  • byType:按类型匹配(默认)。
  • byName:按 Bean 名匹配(用 @Qualifier)。

2.3 配置方式 3:Java 配置(纯代码,零 XML/注解污染)

适合大型项目,配置集中。

示例(基于 2.2 的类,去掉 @Component):

@Configuration
public class AppConfig {
    @Bean
    public Engine engine() { return new PetrolEngine(); }

    @Bean
    public Car car() {
        Car c = new Car();
        c.setEngine(engine()); // 手动 DI
        return c;
    }
}

测试同上:new AnnotationConfigApplicationContext(AppConfig.class)

第三部分:高级玩法——Bean 管理和优化

3.1 Bean 作用域(Scope)

默认 Singleton(单例),但可以改。

Scope描述注解/配置
singleton全局唯一实例默认
prototype每次 getBean 都新创建@Scope(“prototype”)
request每个 HTTP 请求一个@Scope(“request”) (Web)
session每个会话一个@Scope(“session”)

示例:@Scope("prototype") public class Car {}

3.2 Bean 生命周期

IoC 容器管理从创建到销毁的全过程:

  1. 实例化(new)。
  2. 属性注入(DI)。
  3. 初始化(@PostConstruct 或 init-method)。
  4. 使用。
  5. 销毁(@PreDestroy 或 destroy-method)。

自定义:

public class Car {
    @PostConstruct public void init() { System.out.println("Init"); }
    @PreDestroy public void destroy() { System.out.println("Destroy"); }
}

3.3 自动装配(Autowiring)

  • @EnableAutoConfiguration(Spring Boot 用):自动配置常见 Bean(如 DataSource)。
  • 问题:多个同类型 Bean?用 @Primary 标记首选,或 @Qualifier 指定。

第四部分:常见问题 & 踩坑指南

问题/坑原因解决
NoSuchBeanDefinitionExceptionBean 未定义或未扫描检查 @Component / @Bean / 扫描包
UnsatisfiedDependencyException依赖未注入检查 @Autowired / 类型匹配
循环依赖A 依赖 B,B 依赖 A用 Setter 注入 / @Lazy 延迟
XML vs 注解性能XML 解析慢优先注解 / Java 配置
测试时注入失败上下文未加载用 @SpringBootTest

调试技巧:日志级别调到 DEBUG,看 Spring 如何装配 Bean。

第五部分:总结 & 扩展学习

  • 核心回顾:IoC 是反转控制,DI 是注入依赖。Spring 通过容器(ApplicationContext)管理 Bean,实现解耦和灵活。
  • 为什么 Spring 牛? 因为 IoC/DI 让大型项目像“积木”一样搭建,易维护。
  • 下一步:学 AOP(切面编程),然后 Spring Boot(简化版 Spring)。
  • 练习建议:建个小项目,注入 Service 到 Controller,模拟用户注册。

如果你有具体代码问题、或想看 Spring Boot 版示例,继续问我~比如“帮我写个带数据库的 DI 示例”?😄

文章已创建 4237

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部