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

6 阅读5分钟

从零理解 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 类

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 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

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();
    }
}

运行结果:

Hello, I am 张三

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


四、Bean 到底是什么?

通俗定义

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

它和普通 new 的区别

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

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

五、IoC 与 DI 的本质

IoC(Inversion of Control,控制反转)

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

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

DI(Dependency Injection,依赖注入)

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

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

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

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

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

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 从来不神秘,它只是把"创建对象"和"管理依赖"这两件事做到了极致。