JavaEE——Spring IoC与DI核心揭秘:从入门到精通

目录

[一、 核心概念通俗解读](#一、 核心概念通俗解读)

[1. Bean 是什么?](#1. Bean 是什么?)

[2. IoC (Inversion of Control - 控制反转)](#2. IoC (Inversion of Control - 控制反转))

[3. DI (Dependency Injection - 依赖注入)](#3. DI (Dependency Injection - 依赖注入))

[二、 完整 Java 代码演示](#二、 完整 Java 代码演示)

[1. 定义标准 (接口)](#1. 定义标准 (接口))

[2. 定义 Bean (实现类)](#2. 定义 Bean (实现类))

[3. 依赖注入 (使用类)](#3. 依赖注入 (使用类))

[4. 启动容器 (模拟 Main 方法)](#4. 启动容器 (模拟 Main 方法))

[三、 知识点详细解析(结合图片)](#三、 知识点详细解析(结合图片))

[1. 为什么叫"控制反转" (IoC)?](#1. 为什么叫“控制反转” (IoC)?)

[2. 依赖注入 (DI) 的本质](#2. 依赖注入 (DI) 的本质)

[四、 扩展知识讲解 (进阶必读)](#四、 扩展知识讲解 (进阶必读))

[1. 构造器注入 vs 字段注入](#1. 构造器注入 vs 字段注入)

[2. 如果有两个电脑怎么办?(@Qualifier)](#2. 如果有两个电脑怎么办?(@Qualifier))

[3. Bean 的生命周期](#3. Bean 的生命周期)

总结

[总结Bean 的三种注册方式以及依赖注入的方法。举例说明能让我记忆深刻。](#总结Bean 的三种注册方式以及依赖注入的方法。举例说明能让我记忆深刻。)

[第一部分:Bean 的三种注册方式](#第一部分:Bean 的三种注册方式)

[1. XML 配置文件(上古卷轴)](#1. XML 配置文件(上古卷轴))

[2. @Component 及其衍生注解(自动扫描)](#2. @Component 及其衍生注解(自动扫描))

[3. @Configuration + @Bean(Java 配置类)](#3. @Configuration + @Bean(Java 配置类))

[第二部分:依赖注入 (DI) 的三种方式](#第二部分:依赖注入 (DI) 的三种方式)

[1. 字段注入 (Field Injection) ------ @Autowired 放在变量上](#1. 字段注入 (Field Injection) —— @Autowired 放在变量上)

[2. Setter 注入 ------ @Autowired 放在 set 方法上](#2. Setter 注入 —— @Autowired 放在 set 方法上)

[3. 构造器注入 (Constructor Injection) ------ 最强推荐 🏆](#3. 构造器注入 (Constructor Injection) —— 最强推荐 🏆)

一张表总结(截图保存)


这张图片上的文字 "Bean 的管理 (IoC) 与依赖注入 (DI)" 只有短短几个字,但它是整个 Spring 框架的灵魂。如果不理解这句话,就没有真正学会 Spring。

为了让你彻底搞懂,我将通过 "传统代码 vs Spring 代码" 的对比,配合全注释的 Java 代码来为你拆解。


一、 核心概念通俗解读

1. Bean 是什么?

在 Java 中,我们把每一个被 Spring 容器管理的对象,都叫 Bean

  • 以前: 你自己 new User(),这个 User 是你私有的。

  • 现在: 你把 User 交给 Spring 管,这个 User 就变成了 Bean

2. IoC (Inversion of Control - 控制反转)

这是权力的转移

  • 以前 (正向控制): 你想吃苹果,必须自己去买 (在代码里写 new Apple())。

  • 现在 (控制反转): 你想吃苹果,就在桌子上放个盘子喊一声。Spring (容器) 会自动把苹果买好放到你盘子里。创建对象的控制权,从你手里交给了 Spring。

3. DI (Dependency Injection - 依赖注入)

这是 IoC 的具体实现方式

  • Spring 怎么把苹果给你?它把苹果塞进(Inject)你的盘子(变量)里。这就叫依赖注入。

二、 完整 Java 代码演示

为了演示这套机制,我们模拟一个场景:一名程序员 (Programmer) 需要一台电脑 (Computer) 才能工作。

1. 定义标准 (接口)

首先,定义电脑的标准。

Java

java 复制代码
// 这是一个接口,代表电脑的标准
public interface Computer {
    void runCode(); // 电脑必须能运行代码
}
2. 定义 Bean (实现类)

我们要告诉 Spring,这里有一台具体的电脑(比如 Mac),请把它管理起来。

Java

java 复制代码
import org.springframework.stereotype.Component;

/**
 * @Component: 这个注解就像在脑门上贴个标签:"我是 Spring 的 Bean"。
 * Spring 扫描到它时,就会自动执行:Computer macComputer = new MacComputer();
 * 并把它放入 IoC 容器(大仓库)里。
 */
@Component 
public class MacComputer implements Computer {

    @Override
    public void runCode() {
        System.out.println("Mac电脑正在飞速编译代码...");
    }
}
3. 依赖注入 (使用类)

现在有一个程序员类,他需要电脑。注意看他从来没有 new 电脑

Java

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @Component: 程序员也是一个 Bean,也交给 Spring 管理。
 */
@Component
public class Programmer {

    /**
     * @Autowired: 这就是 DI(依赖注入)的核心!
     * 意思:Spring,请去你的仓库(IoC容器)里帮我找一个实现了 Computer 接口的 Bean。
     * 找到后,自动赋值给这个变量。
     * * 如果没有这个注解,computer 变量就是 null,调用会报错。
     */
    @Autowired
    private Computer computer;

    public void work() {
        System.out.println("程序员准备开始工作...");
        // 直接使用 computer,完全不需要自己 new!
        computer.runCode(); 
    }
}
4. 启动容器 (模拟 Main 方法)

最后,我们需要启动 Spring 容器,让这一切发生。

Java

java 复制代码
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @Configuration: 告诉 Spring 这是一个配置类。
 * @ComponentScan: 告诉 Spring 去扫描当前包及其子包,找找有没有加 @Component 的类。
 */
@Configuration
@ComponentScan(basePackages = "com.example.demo") 
public class MainApp {

    public static void main(String[] args) {
        // 1. 启动 IoC 容器
        // 这一步发生时,Spring 会自动创建 MacComputer 和 Programmer 的实例。
        AnnotationConfigApplicationContext context = 
                new AnnotationConfigApplicationContext(MainApp.class);

        System.out.println("容器启动完毕!");

        // 2. 从容器里拿出"程序员" Bean
        // 我们不需要 new Programmer(),而是向容器要。
        Programmer programmer = context.getBean(Programmer.class);

        // 3. 让程序员工作
        // 此时,programmer 内部的 computer 已经被 Spring 自动注入好了。
        programmer.work(); 
        
        // 关闭容器
        context.close();
    }
}

三、 知识点详细解析(结合图片)

1. 为什么叫"控制反转" (IoC)?
  • 控制权: 指的是"谁负责创建对象"和"谁负责维护对象之间的关系"。

  • 正向(传统):

    Java

    java 复制代码
    // 这种写法,Programmer 必须知道 MacComputer 的存在,耦合度极高。
    // 如果以后想换成 Windows,必须修改 Programmer 的代码。
    Computer computer = new MacComputer(); 
  • 反转(Spring):

    Java

    java 复制代码
    // Programmer 根本不知道 computer 是 Mac 还是 Windows。
    // 只要 Spring 给它塞进去什么,它就用什么。
    @Autowired
    private Computer computer; 

    控制权从"程序员"手里反转到了"Spring 容器"手里。

2. 依赖注入 (DI) 的本质

DI 是 IoC 的手段。IoC 是思想,DI 是做法。

Spring 就像一个全自动管家:

  1. 扫描: 发现你需要一个 Computer

  2. 寻找: 在它的仓库里找找有没有 MacComputer

  3. 注入: 偷偷把 MacComputer 赋值给你的变量。


四、 扩展知识讲解 (进阶必读)

1. 构造器注入 vs 字段注入

上面的例子用的是字段注入(直接在变量上加 @Autowired),这最简单,但不被推荐用于生产环境的复杂业务。

推荐写法(构造器注入):

Java

java 复制代码
@Component
public class Programmer {

    private final Computer computer; // 可以声明为 final,更安全

    // Spring 4.3+ 以后,如果只有一个构造方法,@Autowired 可以省略
    // 好处:这就强迫你必须给 Programmer 一台电脑,否则连对象都造不出来,避免了空指针风险。
    public Programmer(Computer computer) {
        this.computer = computer;
    }
}
2. 如果有两个电脑怎么办?(@Qualifier)

如果你的仓库里既有 MacComputer 也有 WindowsComputer(都加了 @Component),Spring 会懵圈:"你要哪一个?"

这时会报错:NoUniqueBeanDefinitionException。

解决办法:指定名字

Java

java 复制代码
@Autowired
@Qualifier("macComputer") // 指定 Bean 的名字(默认是类名首字母小写)
private Computer computer;
3. Bean 的生命周期

你把对象交给 Spring 管理后,Spring 不仅仅是帮你 new 一下那么简单。它还会负责:

  • 初始化前/后: 执行 @PostConstruct 注解的方法。

  • 销毁时: 执行 @PreDestroy 注解的方法(比如关闭数据库连接)。

总结

图片中的那行字,用一句话概括就是:

不要自己 new 对象,在类上加 @Component 交给 Spring (IoC),在需要用的属性上加 @Autowired 让 Spring 填给你 (DI)。

总结Bean 的三种注册方式以及依赖注入的方法。举例说明能让我记忆深刻。

这是一个完美的总结时刻!为了让你不仅记住代码,还能记住原理,我们用一个**"组建超级英雄联盟"**的故事来比喻。


第一部分:Bean 的三种注册方式

(潜台词:如何把英雄招募进联盟?)

Spring 容器就是"英雄联盟基地"。我们需要把各种英雄(对象)注册进去。

1. XML 配置文件(上古卷轴)
  • 比喻: 纸质花名册。

    在几十年前,没有电脑,神盾局要把英雄名字写在一个厚厚的本子上。

  • 特点: 古老、繁琐、要把类和配置分开写。现在除了维护老古董系统,基本没人用了。

  • 代码长相:

    XML

    java 复制代码
    <bean id="ironMan" class="com.marvel.IronMan" />
2. @Component 及其衍生注解(自动扫描)
  • 比喻: 自带工牌,刷脸进门。

    英雄们(类)自己在脑门上贴个二维码(@Component / @Service / @Controller)。基地的监控系统(Spring Boot)一扫,自动识别:"哦,你是自己人,进来吧!"

  • 适用场景: 你自己写的代码

  • 代码长相:

    Java

    java 复制代码
    @Component // <--- 脑门贴条:我是英雄
    public class IronMan { ... }
3. @Configuration + @Bean(Java 配置类)
  • 比喻: 外包合同 / 进口采购。

    有些装备(比如"雷神之锤"),它不是人,也没法自己在脑门贴条(因为它是第三方库里的类,你改不了它的源码)。

    这时候你需要一个专门的"采购员"(配置类),手动把它造出来,送进基地。

  • 适用场景: 引入第三方库(如数据库连接池、Gson、Redis客户端)。

  • 代码长相:

    Java

    java 复制代码
    @Configuration // <--- 我是采购部
    public class WeaponConfig {
        @Bean // <--- 这是采购回来的宝贝,也要入库
        public Hammer mjolnir() {
            return new Hammer("雷神之锤");
        }
    }

第二部分:依赖注入 (DI) 的三种方式

(潜台词:英雄进门了,怎么把武器塞给他是最稳的?)

假设:钢铁侠 (IronMan) 必须要有 反应堆 (Reactor) 才能飞。

1. 字段注入 (Field Injection) ------ @Autowired 放在变量上
  • 比喻: 隔空变物(魔法)。

    钢铁侠两手空空走进基地,Spring 会魔法,直接把反应堆"变"进他的胸口里。

  • 缺点: 太依赖魔法。如果脱离了 Spring 环境(比如你自己写单元测试),这招魔法就失效了,钢铁侠胸口是空的,直接空指针异常。

  • 推荐度: ⭐⭐(写起来最爽,但官方不推荐,容易造成循环依赖和测试困难)。

    Java

    java 复制代码
    @Component
    public class IronMan {
        @Autowired // <--- 魔法注入
        private Reactor reactor;
    }
2. Setter 注入 ------ @Autowired 放在 set 方法上
  • 比喻: 快递补给。

    钢铁侠先裸身进来,然后有人给他送个快递:"嘿,你的反应堆到了,装上吧。"

  • 特点: 装备可拆卸。钢铁侠可以一会装这个反应堆,一会换那个。

  • 适用场景: 可选依赖(比如这个装备不是必须的,没有也能活)。

    Java

    java 复制代码
    @Component
    public class IronMan {
        private Reactor reactor;
    
        @Autowired // <--- 什么时候想用,什么时候装
        public void setReactor(Reactor reactor) {
            this.reactor = reactor;
        }
    }
3. 构造器注入 (Constructor Injection) ------ 最强推荐 🏆
  • 比喻: 进门安检强制发放。

    基地的安检门规定:"钢铁侠,如果你手里没有拿反应堆,就不许进这个门(不许被创建)!"

  • 优点: 绝对安全

    1. 只要钢铁侠这个对象存在,他一定有反应堆(不会空指针)。

    2. 一旦装上,就焊死了(可以加 final),谁也别想偷偷换掉(不可变性)。

  • 推荐度: ⭐⭐⭐⭐⭐(Spring 官方唯一指定最佳实践)。

    Java

    java 复制代码
    @Component
    public class IronMan {
        private final Reactor reactor; // 加上 final,焊死!
    
        // 构造方法:想出生?先拿装备!
        // (Spring 4.3以后,如果只有一个构造方法,@Autowired 甚至可以省略)
        public IronMan(Reactor reactor) {
            this.reactor = reactor;
        }
    }

一张表总结(截图保存)

核心问题 方式 记忆口诀 适用/优缺点
注册 Bean XML 老账本 过时,繁琐。
(怎么进门?) @Component 刷脸卡 最常用,用于自己写的类。
@Bean 搞外包 用于第三方类(改不了源码时)。
依赖注入 字段注入 变魔术 写着快,测着坑(容易空指针)。
(怎么给装备?) Setter 收快递 灵活,可选,但装备容易丢。
构造器 死规定 最推荐,必须有装备才能出生,安全可靠。

这样是不是清晰多了?下次写代码时,想象一下你是基地的管理员,正在给英雄发装备,你就知道该选哪种了!

相关推荐
咖啡八杯1 天前
GoF设计模式——备忘录模式
java·后端·spring·设计模式
Flittly2 天前
【AgentScope Java新手村系列】(16)从RAG到多路检索
java·spring boot·spring
咖啡八杯3 天前
GoF设计模式——中介者模式
java·后端·spring·设计模式
Flittly5 天前
【AgentScope Java新手村系列】(14)人机交互
java·spring boot·spring
唐青枫9 天前
Java Spring WebFlux 实战指南:用 Mono、Flux 和 WebClient 写响应式接口
java·spring
咖啡八杯10 天前
GoF设计模式——策略模式
java·后端·spring·设计模式
Flittly11 天前
【AgentScope Java新手村系列】(11)中断与恢复
java·spring boot·spring
dunky12 天前
Spring 的三级缓存与循环依赖
后端·spring
码云数智-园园16 天前
C++20 Modules 模块详解
java·开发语言·spring
咖啡八杯16 天前
GoF设计模式——享元模式
java·spring·设计模式·享元模式