前言
大家好,这里是程序员阿亮!
大家在学习了Java生态,如Spring、SpringBoot之后,一定见过IoC,这是我们开发中经常使用的思想,在我们的框架中如Spring就广泛使用到。
在Go语言的一些框架中虽然没有特意的去实现IoC,但是我们也可以通过一些额外的库去使用,因为IoC可以让我们统一管理对象生命周期,提高扩展,并且降低耦合,也可以让我们更好地测试代码!
一、IOC是什么?
1.1 类比场景
假设你要组装一台电脑。
-
传统方式(没有 IoC):
你需要亲自 去买 CPU、买显卡、买内存条,然后自己把它们插在主板上。如果 CPU 型号变了,你可能得把主板也换了。
代码体现: 在 Service 类里直接 new Dao() 。你(Service)控制着依赖对象(Dao)的生杀大权。
-
IoC 方式:
你直接找一家"电脑组装托管中心"(容器)。你告诉它:"我要一台电脑,配置清单给你。"
当你拿到电脑时,CPU、内存都已经插好了。你不需要知道 CPU 是哪里买的,怎么插进去的。
代码体现: Service 类里只定义一个 Dao 接口类型的变量,容器在运行时自动把 Dao 的实例"塞"进去。
1.2 核心概念
IoC (Inversion of Control) ,即控制反转。
-
谁控制谁?
-
传统模式:应用程序(调用者)直接控制依赖对象的创建和生命周期。
-
IoC 模式:IoC 容器控制对象的创建和注入。
-
-
反转了什么?
- 控制权发生了反转。获取依赖对象的过程,由"主动索取"变成了"被动接收"。
好莱坞原则 (The Hollywood Principle) :
"Don't call us, we'll call you." (别打电话给我们,有事我们会打给你。)
这就是 IoC 的生动写照。
1.3 IoC 与 DI的关系
很多人分不清 IoC 和 DI(Dependency Injection,依赖注入)。
-
IoC 是一种设计思想或原则。
-
DI 是实现 IoC 的一种具体手段/技术。
结论 :Spring 通过 DI(依赖注入) 实现 了 IoC(控制反转)。
二、为什么要IoC?
如果只是为了把 new 换成 @Autowired,那不仅没省事,反而复杂了。IoC 真正解决的是 耦合度(Coupling) 问题。
-
解耦 (Decoupling):
调用者不再依赖具体的实现类,只依赖接口。更换实现类时(例如从 MySQL 换到 Oracle),只需要修改配置文件或注解,不需要修改调用者的代码。
-
可测试性 (Testability):
在做单元测试时,因为对象不是 new 出来的,我们可以轻松地编写 Mock 对象(假对象)注入进去,单独测试业务逻辑。
-
单例与资源管理:
Spring 容器默认是单例模式,可以避免频繁创建大对象(如数据库连接池、Service 组件),节省内存和 CPU。
三、Spring如何实现IoC?
Spring 实现 IoC 的过程,说白了就是:解析配置 -> 生成定义 -> 反射创建 -> 属性填充 -> 放入单例池。
我们可以把 Spring 容器想象成一个巨大的工厂,其核心实现依赖于以下几个关键要素:
1. 核心容器:ApplicationContext 与 BeanFactory
-
BeanFactory:Spring 最底层的接口,提供了最简单的容器功能(懒加载)。
-
ApplicationContext:BeanFactory 的子接口,功能更强大(预加载、国际化、事件传播等),我们平时使用的基本都是它。
2. 万物之源:Map<String, Object>
Spring 容器在底层到底是什么?
剥去层层封装,它本质上就是一个 ConcurrentHashMap。
java
// 简化版示意图:单例池 (Singleton Objects)
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
当我们需要用 Bean 时,Spring 就是从这个 Map 里 get 出来的。
3. 核心流程:Spring 启动时发生了什么?
Spring 容器启动并实现注入的简易流程如下:
第一步:资源定位与读取 (Resource Loading)
Spring 扫描你的 XML 文件或 @Component、@Service 注解,找到所有需要托管的类。
第二步:生成 BeanDefinition (最关键的一步)
Spring 不会直接上来就 new 对象。它会先为每个类生成一个 BeanDefinition(Bean 定义信息)。
-
BeanDefinition 就像是"图纸"或"菜谱"。
-
它记录了:这个 Bean 的类名是什么?是单例还是多例?有没有 @Lazy?依赖了哪些其他 Bean?
第三步:实例化 (Instantiation) - 使用反射
Spring 拿到 BeanDefinition 后,通过 Java 反射机制 (Reflection) 来创建对象实例。
java
// 伪代码演示反射创建
Class<?> clazz = Class.forName(beanDefinition.getClassName());
Object instance = clazz.getDeclaredConstructor().newInstance();
我们可以通过反射获取堆内存中类加载进来的Class对象,如何通过Class对象去创建对象实例。
第四步:依赖注入 (Populate Bean)
Spring 检查这个对象里有哪些字段被标记了 @Autowired。
-
它会去容器(Map)里找对应的依赖对象。
-
通过反射的 **field.set(instance, dependency)**将依赖注入进去。
第五步:初始化 (Initialization)
执行各种生命周期回调(如 PostConstruct、InitializingBean),如果配置了 AOP,这里还会利用动态代理生成代理对象。
第六步:放入单例池
最终的成品对象被放入 singletonObjects 这个 Map 中,供程序随时调用。
四、手写一个Spring的IoC
通过上面的解释,我们会发现实际上IoC就是利用我们的反射与Map进行的,操作其实很单纯,所以要写一个简单的IoC其实不难。
1. 定义注解
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyComponent {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyAutowired {}
2. 简易容器实现
java
public class MySpringContext {
// 这里的 Map 就是传说中的 IOC 容器
private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
public MySpringContext(Class configClass) throws Exception {
// 1. 扫描配置类同包下的所有类(简化版扫描)
String packageName = configClass.getPackage().getName();
// ... (省略遍历文件查找 .class 的代码) ...
List<Class<?>> classList = findClasses(packageName);
// 2. 实例化对象 (只处理带 @MyComponent 的)
for (Class<?> clazz : classList) {
if (clazz.isAnnotationPresent(MyComponent.class)) {
MyComponent component = clazz.getAnnotation(MyComponent.class);
String beanName = component.value().equals("") ? toLowerCaseFirstOne(clazz.getSimpleName()) : component.value();
// 反射创建对象
Object instance = clazz.getDeclaredConstructor().newInstance();
singletonObjects.put(beanName, instance);
}
}
// 3. 依赖注入 (处理 @MyAutowired)
for (Object object : singletonObjects.values()) {
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(MyAutowired.class)) {
field.setAccessible(true);
String fieldName = field.getName();
// 从容器中找对应的 Bean
Object dependency = singletonObjects.get(fieldName);
// 反射注入
field.set(object, dependency);
}
}
}
}
public Object getBean(String beanName) {
return singletonObjects.get(beanName);
}
}
总结
Spring 的 IoC 并没有什么黑魔法,它的底层核心在于:
-
设计模式:工厂模式(BeanFactory)+ 单例模式(Singleton)。
-
技术手段 :Java 反射 (Reflection) 是实现动态创建和注入的基石。
-
数据结构 :Map 是存放所有 Bean 的大仓库。
-
流程:BeanDefinition(图纸) -> Instantiation(反射创建裸对象) -> Populate(反射注入属性) -> Initialization(初始化及代理)。
理解IoC会发现Spring作为一个框架,也是通过我们的代码实现的,并不复杂!

