从零理解 Spring 核心:IoC 容器与依赖注入,以及手写一个迷你版

从零理解 Spring 核心:IoC 容器与依赖注入,以及手写一个迷你版

学 Spring,最难迈过去的坎不是配置,而是脑子里没有"容器"这个概念。本文从最朴素的代码出发,带你彻底搞懂 Bean、IoC 和 DI,最后手写一个可运行的迷你 IoC 容器。


一、Spring 是什么?先建立整体认知

在 Java 世界里,Spring 几乎是企业级开发的事实标准。它的核心由几个层次组成:

  • Spring 容器:一个 IoC 容器,这是 Spring 最底层、最核心的部分
  • Spring MVC:构建在 Spring 容器和 Servlet 之上的 Web 应用框架
  • Spring Boot:在 Spring 基础上进一步封装,提供更高的集成度和自动化配置能力

理解 Spring,必须先理解容器 。而理解容器,必须先理解 Bean


二、核心概念速览

概念 一句话解释
Bean 容器中管理的最小工作单元,本质是一个 Java 对象
BeanFactory / ApplicationContext 容器本身对应的 Java 对象,负责管理所有 Bean
IoC(控制反转) 对象的创建权,从你手里反转给了 Spring 容器
DI(依赖注入) 容器自动把需要的依赖塞进去,不需要你手动 new

这四个概念不是割裂的,它们共同描述同一件事:你只负责声明,Spring 负责兜底。


三、最经典的 Spring Bean 示例(XML 配置)

ClassPathXmlApplicationContext 这种经典方式,三步让你瞬间感受什么是 Bean。

第 1 步:写一个普通 Java 类

typescript 复制代码
public class User {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void sayHello() {
        System.out.println("Hello, I am " + name);
    }
}

注意:这就是一个普通 Java 对象,和 Spring 完全没有关系,没有继承任何父类,没有实现任何接口。Spring 的非侵入性,从这里就开始体现。


第 2 步:编写 Spring 配置文件 beans.xml

放在项目的 resources 目录下:

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="user" class="com.example.User">
        <property name="name" value="张三"/>
    </bean>
</beans>

这段 XML 的含义:

  • id="user":Bean 在容器中的唯一名字,相当于变量名
  • class="com.example.User":告诉容器要实例化哪个类
  • <property name="name" value="张三"/>:容器会调用 setName("张三") 完成属性赋值

这就是所谓的 Bean 定义 ------你只是在"描述"对象,还没有任何 new


第 3 步:启动容器,取出 Bean

java 复制代码
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {

        // 1. 加载 Spring 配置文件 → 启动容器
        ApplicationContext context =
            new ClassPathXmlApplicationContext("beans.xml");

        // 2. 从容器中取出 Bean
        User user = (User) context.getBean("user");

        // 3. 调用方法
        user.sayHello();
    }
}

运行结果:

css 复制代码
Hello, I am 张三

这个简单的例子背后藏着 Spring 最核心的设计哲学:你没有 new User() ,是容器帮你创建的,并且帮你完成了属性注入。


四、Bean 到底是什么?

通俗定义

Bean = Spring 容器负责创建、管理、注入生命周期的 Java 对象。

它和普通 new 的区别

ini 复制代码
// 普通对象:你控制,容器不知道它的存在
User u1 = new User();

// Spring Bean:容器控制,生命周期由容器负责
User u2 = (User) context.getBean("user");
对比维度 普通 new Spring Bean
创建者 你自己 Spring 容器
容器感知
依赖注入 手动处理 容器自动完成
生命周期管理 由 GC 决定 容器负责创建、初始化、销毁

五、IoC 与 DI 的本质

IoC(Inversion of Control,控制反转)

传统写法里, 决定什么时候 new 一个对象;引入 Spring 后,这个"控制权"交给了容器,这就叫控制反转

arduino 复制代码
你 → new 对象        (传统,控制权在你手里)
容器 → new 对象       (IoC,控制权反转给容器)

DI(Dependency Injection,依赖注入)

DI 是实现 IoC 的具体手段。当 A 对象需要 B 对象时,不需要在 A 内部 new B(),而是由容器自动把 B 注入进 A。

java 复制代码
// 传统:手动处理依赖
class UserService {
    UserDao userDao = new UserDao(); // 强耦合
}

// Spring DI:容器注入
class UserService {
    @Autowired
    UserDao userDao; // 容器负责,解耦
}

六、手写迷你 IoC 容器(支持自动 DI)

理解了概念,我们来揭开 Spring 的神秘面纱------用纯 Java 手写一个支持依赖注入的迷你 IoC 容器,不依赖任何框架,看完你就明白 Spring 在做什么。

typescript 复制代码
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Field;

/**
 * 迷你 IoC 容器 + 自动依赖注入(极简版)
 */
public class MiniIocWithDI {

    // Bean 容器:本质就是一个 Map
    private final Map<String, Object> beanMap = new HashMap<>();

    /** 注册 Bean */
    public void registerBean(String name, Object bean) {
        beanMap.put(name, bean);
    }

    /** 获取 Bean */
    public Object getBean(String name) {
        return beanMap.get(name);
    }

    /**
     * 核心:自动注入所有带 @MyAutowired 注解的字段
     * 这里模拟的就是 Spring 的依赖注入过程
     */
    public void injectDependencies() {
        for (Object bean : beanMap.values()) {
            Class<?> clazz = bean.getClass();

            // 遍历该 Bean 的所有字段
            for (Field field : clazz.getDeclaredFields()) {

                // 如果字段上标注了 @MyAutowired
                if (field.isAnnotationPresent(MyAutowired.class)) {

                    // 按字段类型,从容器中查找对应的 Bean
                    Class<?> fieldType = field.getType();
                    Object dependBean = findBeanByType(fieldType);

                    if (dependBean != null) {
                        field.setAccessible(true); // 突破 private 限制
                        try {
                            field.set(bean, dependBean); // 注入!
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    /** 根据类型从容器中查找 Bean */
    private Object findBeanByType(Class<?> type) {
        for (Object bean : beanMap.values()) {
            if (type.isInstance(bean)) {
                return bean;
            }
        }
        return null;
    }

    // ==================== 模拟 @Autowired 注解 ====================

    public @interface MyAutowired {}

    // ========================= 业务 Bean ==========================

    public static class UserDao {
        public String getName() {
            return "来自 UserDao:张三";
        }
    }

    public static class UserService {
        @MyAutowired          // 声明需要注入
        private UserDao userDao;

        public void hello() {
            System.out.println(userDao.getName());
        }
    }

    // ========================== 测试入口 ==========================

    public static void main(String[] args) {
        MiniIocWithDI ioc = new MiniIocWithDI();

        // 1. 将 Bean 注册到容器
        ioc.registerBean("userDao", new UserDao());
        ioc.registerBean("userService", new UserService());

        // 2. 容器执行自动依赖注入
        ioc.injectDependencies();

        // 3. 直接使用,userDao 已经被自动注入
        UserService userService = (UserService) ioc.getBean("userService");
        userService.hello();
    }
}

运行输出:

复制代码
来自 UserDao:张三

这个 Demo 实现了什么?

特性 实现方式
IoC 容器 HashMap 存储所有 Bean,统一管理
依赖注入 通过 Java 反射扫描字段上的 @MyAutowired,自动赋值
解耦 UserService 完全不知道 UserDao 是怎么来的
无侵入 Bean 本身不需要继承或实现任何框架类

七、一句话总结 Spring 的本质

Spring IoC = 一个 Map 存 Bean
Spring DI = 反射 + 注解自动赋值

当你下次看到 @Autowired@Component@Bean 这些注解时,脑子里应该浮现的画面是:Spring 在用反射扫描你的代码,把对象放进 Map,然后再从 Map 里把依赖取出来塞给需要它的字段。


八、小结

通过本文,我们从最基础的 XML Bean 定义,到理解 IoC/DI 的思想,再到手写一个完整的迷你容器,走了一遍 Spring 最核心的设计路径。理解了这些,后续无论是注解驱动配置(@ComponentScan)、还是 Spring Boot 的自动装配,都不过是在这个基础上的延伸和增强。

Spring 从来不神秘,它只是把"创建对象"和"管理依赖"这两件事做到了极致。

相关推荐
148612 小时前
Redis 删除缓存失败怎么办?重试、死信、补偿的工程化方案
后端
None3212 小时前
NestJS 流式文件上传实践:从 Multer 到 Busboy 的进阶之路
前端·后端
148612 小时前
MySQL 复合索引怎么设计?从业务 SQL 反推索引顺序
后端
DROm RAPS2 小时前
十七:Spring Boot依赖 (2)-- spring-boot-starter-web 依赖详解
前端·spring boot·后端
TlYf NTLE3 小时前
Spring Boot spring-boot-maven-plugin 参数配置详解
spring boot·后端·maven
皮卡丘love大米3 小时前
【避坑实录】MongoDB 8.0 Windows 服务安装报错 2186 / PowerShell sc 失效 完美解决
后端
a8a3023 小时前
Spring Boot(快速上手)
java·spring boot·后端
武子康3 小时前
大数据-258 离线数仓 - Livy与Griffin编译安装指南:大数据环境配置实战
大数据·hadoop·后端
野犬寒鸦3 小时前
Redis复习记录Day03
服务器·redis·后端·面试·bootstrap·mybatis