从零理解 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");
| 对比维度 | 普通 new | Spring 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 从来不神秘,它只是把"创建对象"和"管理依赖"这两件事做到了极致。