当前位置: 首页 > article >正文

《从零构建一个简易的IOC容器,理解Spring的核心思想》

大家好呀!今天我们要一起探索Java开发中最神奇的魔法之一 —— Spring框架的IOC容器!🧙‍♂️ 我会用最最最简单的方式,让你彻底明白这个看似高深的概念。准备好了吗?Let’s go! 🚀

一、什么是IOC容器?🍯

想象你有一个超级大的玩具箱🎁,里面装满了各种玩具。以前你要玩某个玩具时,得自己伸手进去找(new 对象())。现在有了IOC容器,就像有个智能机器人🤖帮你管理玩具箱,你只需要说:“我要玩小汽车!🚗”,机器人就会自动找到并递给你 —— 这就是IOC(控制反转)!

专业点说:IOC(Inversion of Control)控制反转就是把创建和管理对象的控制权从程序员手中"反转"给了容器。

二、为什么要用IOC?🤔

举个生活中的例子🌰:

没有IOC时:

// 你要喝咖啡,得自己种咖啡豆、磨粉、冲泡...
Coffee coffee = new Coffee();
coffee.drink();

有IOC时:

// 只需要去咖啡店说"我要一杯咖啡"
@Autowired
Coffee coffee;  // 咖啡自动送到你面前
coffee.drink();

IOC的好处:

  1. 不用自己new对象了,省事!😌
  2. 方便统一管理对象
  3. 降低代码耦合度(类之间不那么依赖了)
  4. 更容易测试和维护

三、手写迷你IOC容器实战 ✍️

现在,让我们从零开始造一个超简易IOC容器!分三步走:

第1步:创建容器类 🏗️

public class MyMiniContainer {// 用来存放所有bean的Map,key是名字,value是对象private Map beans = new HashMap<>();// 注册bean的方法public void registerBean(String name, Object bean) {beans.put(name, bean);}// 获取bean的方法public Object getBean(String name) {return beans.get(name);}
}

第2步:测试我们的容器 🧪

public class Test {public static void main(String[] args) {// 1. 创建容器MyMiniContainer container = new MyMiniContainer();// 2. 创建对象并放入容器UserService userService = new UserServiceImpl();container.registerBean("userService", userService);// 3. 需要时从容器获取UserService service = (UserService) container.getBean("userService");service.sayHello();  // 输出: Hello World!}
}

第3步:实现自动依赖注入 🎯

上面的容器太简单了,我们来升级它,实现自动"@Autowired"功能!

public class EnhancedContainer {private Map beans = new HashMap<>();// 新增:根据类型自动注入依赖public void autowire(Object bean) throws Exception {Field[] fields = bean.getClass().getDeclaredFields();for (Field field : fields) {if (field.isAnnotationPresent(Autowired.class)) {// 获取字段类型Class fieldType = field.getType();// 从容器找对应类型的实例Object dependency = findBeanByType(fieldType);// 设置字段值field.setAccessible(true);field.set(bean, dependency);}}}// 根据类型查找beanprivate Object findBeanByType(Class type) {for (Object bean : beans.values()) {if (type.isAssignableFrom(bean.getClass())) {return bean;}}throw new RuntimeException("找不到类型为 " + type.getName() + " 的bean");}
}

四、Spring IOC容器的完整实现思路 🧩

现在让我们看看真正的Spring IOC是怎么做的(简化版):

  1. 配置读取阶段 📖

    • 读取XML配置或扫描注解
    • 识别哪些类需要被管理
  2. 实例化阶段 🏭

    • 通过反射创建Bean实例
    • 放到一个叫"BeanFactory"的大Map里
  3. 依赖注入阶段 💉

    • 检查每个Bean的@Autowired注解
    • 把依赖的其他Bean注入进去
  4. 初始化阶段 🎉

    • 调用初始化方法
    • 处理AOP代理等增强功能

五、完整手写IOC容器代码 🖥️

下面是一个相对完整的简易IOC容器实现:

// 自定义Autowired注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
}// 自定义Component注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {String value() default "";
}// IOC容器核心类
public class MyIOCContainer {private Map beans = new ConcurrentHashMap<>();// 初始化容器public void init(String basePackage) throws Exception {// 1. 扫描包路径下的所有类Set> classes = scanPackage(basePackage);// 2. 创建所有带有@MyComponent注解的类的实例createBeans(classes);// 3. 自动注入依赖autowireBeans();}// 扫描包路径下的所有类private Set> scanPackage(String basePackage) {// 实现略,可以使用反射工具包return new HashSet<>();}// 创建Bean实例private void createBeans(Set> classes) throws Exception {for (Class clazz : classes) {if (clazz.isAnnotationPresent(MyComponent.class)) {MyComponent component = clazz.getAnnotation(MyComponent.class);String beanName = component.value().isEmpty() ? clazz.getSimpleName() : component.value();Object instance = clazz.getDeclaredConstructor().newInstance();beans.put(beanName, instance);}}}// 自动注入依赖private void autowireBeans() throws Exception {for (Object bean : beans.values()) {autowireBean(bean);}}// 为单个Bean注入依赖private void autowireBean(Object bean) throws Exception {Field[] fields = bean.getClass().getDeclaredFields();for (Field field : fields) {if (field.isAnnotationPresent(MyAutowired.class)) {Object dependency = findBeanByType(field.getType());field.setAccessible(true);field.set(bean, dependency);}}}// 根据类型查找Beanprivate Object findBeanByType(Class type) {for (Object bean : beans.values()) {if (type.isAssignableFrom(bean.getClass())) {return bean;}}throw new RuntimeException("找不到类型为 " + type.getName() + " 的bean");}// 获取Beanpublic Object getBean(String name) {return beans.get(name);}
}

六、Spring IOC的更多魔法 ✨

真正的Spring IOC容器比我们的简易版强大得多,它还有:

  1. Bean作用域 🔍

    • Singleton:单例(默认)
    • Prototype:每次获取新实例
    • Request/Session/Application:Web相关作用域
  2. 生命周期回调

    • @PostConstruct:初始化方法
    • @PreDestroy:销毁前方法
  3. 条件化Bean ☑️

    • @Conditional:满足条件才创建Bean
  4. Bean后处理器 🔧

    • BeanPostProcessor:对Bean进行额外处理

七、面试常问的IOC问题 💼

  1. IOC和DI有什么区别?

    • IOC是思想(控制反转)
    • DI是实现方式(依赖注入)
    • 好比:IOC是"不用自己做饭",DI是"外卖送到家" 🍔
  2. Spring容器启动流程是怎样的?

    1. 加载配置
    2. 解析成BeanDefinition
    3. 注册到BeanFactory
    4. 实例化非懒加载的单例Bean
    5. 发布容器启动事件
  3. 循环依赖怎么解决?

    • Spring使用三级缓存:
      • 一级缓存:完整Bean
      • 二级缓存:早期暴露的Bean(还没注入属性)
      • 三级缓存:Bean工厂(能创建Bean)

八、实际项目中的应用案例 🏢

假设我们在开发一个电商系统🛒:

@MyComponent
public class OrderService {@MyAutowiredprivate PaymentService paymentService;@MyAutowired private InventoryService inventoryService;public void placeOrder(Order order) {inventoryService.checkStock(order);paymentService.processPayment(order);// 创建订单...}
}// 使用时:
public class Main {public static void main(String[] args) throws Exception {MyIOCContainer container = new MyIOCContainer();container.init("com.ecommerce");OrderService orderService = (OrderService) container.getBean("orderService");orderService.placeOrder(new Order());}
}

九、性能优化小贴士 ⚡

  1. 合理使用作用域

    • 无状态服务用Singleton
    • 有状态服务考虑Prototype
  2. 延迟加载

    • @Lazy注解减少启动时间
  3. 避免过度依赖注入

    • 一个类最好不要超过5个依赖
  4. 使用构造器注入

    • 比字段注入更利于测试和不变性

十、常见错误排查 🚨

  1. NoSuchBeanDefinitionException

    • 检查是否加了@Component
    • 扫描包路径是否正确
  2. BeanCurrentlyInCreationException(循环依赖):

    • 使用@Lazy打破循环
    • 重构代码解耦
  3. 注入的Bean为null

    • 检查是否在容器外使用@Autowired
    • 字段是否是private

十一、总结 🎓

今天我们从小白的角度,一步步揭开了Spring IOC容器的神秘面纱:

  1. IOC就像智能玩具箱🤖,帮你管理所有对象
  2. 核心思想是"控制反转"和"依赖注入"
  3. 自己动手实现了一个迷你IOC容器
  4. 了解了Spring容器的更多高级特性

记住,理解IOC的关键是明白:不要来找我,我会去找你 —— 这就是控制反转的精髓!💡

希望这篇文章能让你对Spring IOC有全新的认识!如果有任何问题,欢迎留言讨论~ 😊

思考题:如果让你给这个迷你容器添加AOP功能,你会怎么设计呢?🤔

推荐阅读文章

  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)

  • 如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

  • HTTP、HTTPS、Cookie 和 Session 之间的关系

  • 什么是 Cookie?简单介绍与使用方法

  • 什么是 Session?如何应用?

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程

  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

  • 如何理解应用 Java 多线程与并发编程?

  • 把握Java泛型的艺术:协变、逆变与不可变性一网打尽

  • Java Spring 中常用的 @PostConstruct 注解使用总结

  • 如何理解线程安全这个概念?

  • 理解 Java 桥接方法

  • Spring 整合嵌入式 Tomcat 容器

  • Tomcat 如何加载 SpringMVC 组件

  • “在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”

  • “避免序列化灾难:掌握实现 Serializable 的真相!(二)”

  • 如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)

  • 解密 Redis:如何通过 IO 多路复用征服高并发挑战!

  • 线程 vs 虚拟线程:深入理解及区别

  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别

  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!

  • “打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”

  • Java 中消除 If-else 技巧总结

  • 线程池的核心参数配置(仅供参考)

  • 【人工智能】聊聊Transformer,深度学习的一股清流(13)

  • Java 枚举的几个常用技巧,你可以试着用用

  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)

  • 如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

  • HTTP、HTTPS、Cookie 和 Session 之间的关系

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程

  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

  • Java Spring 中常用的 @PostConstruct 注解使用总结

  • 线程 vs 虚拟线程:深入理解及区别

  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别

  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!

  • 探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)

  • 为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)

相关文章:

《从零构建一个简易的IOC容器,理解Spring的核心思想》

大家好呀&#xff01;今天我们要一起探索Java开发中最神奇的魔法之一 —— Spring框架的IOC容器&#xff01;&#x1f9d9;‍♂️ 我会用最最最简单的方式&#xff0c;让你彻底明白这个看似高深的概念。准备好了吗&#xff1f;Let’s go! &#x1f680; 一、什么是IOC容器&…...

QSFP+、QSFP28、QSFP-DD接口分别实现40G、100G、200G/400G以太网接口

常用的光模块结构形式&#xff1a; 1&#xff09;QSFP等效于4个SFP&#xff0c;支持410Gbit/s通道传输&#xff0c;可通过4个通道实现40Gbps传输速率。与SFP相比&#xff0c;QSFP光模块的传输速率可达SFP光模块的四倍&#xff0c;在部署40G网络时可直接使用QSFP光模块&#xf…...

tensorflow 1.x

简介 TensorFlow&#xff1a;2015年谷歌&#xff0c;支持python、C&#xff0c;底层是C&#xff0c;主要用python。支持CNN、RNN等算法&#xff0c;分CPU TensorFlow/GPU TensorFlow。 TensorBoard&#xff1a;训练中的可视化。 快捷键&#xff1a;shiftenter执行命令,Tab键进…...

vue3模版语法

Vue 的模板语法&#xff08;template syntax&#xff09;是 Vue 框架中用于声明式地绑定 DOM 的方式&#xff0c;核心是将 HTML 与 Vue 实例的数据绑定起来。 下面是常用的 Vue 模板语法总结&#xff08;以 Vue 3 Composition API 为基础&#xff0c;也适用于 Vue 2 的 Options…...

java加强 -List集合

List集合是Collection集合下的集合的一种&#xff0c;它有序&#xff0c;可重复&#xff0c;有索引。但由于存在不同的底层实现方法&#xff0c;适合的场景也不同。 ArrayList底层是基于数组存储数据的&#xff0c;而LinkedList底层是基于链表存储数据的。因此&#xff0c;前者…...

PXE安装Ubuntu系统

文章目录 1. 服务器挂载Ubuntu镜像2. 修改dhcp配置文件3. 修改tftp配置文件4.复制网络驱动文件和其他配置文件5. http目录下配置文件6. 踩坑记录6.1 Failed to load ldlinux.c326.2 no space left on device6.3 为啥用pxe安装系统时&#xff0c;客户端需要较大的内存&#xff1…...

uniapp tabBar 中设置“custom“: true 在H5和app中无效解决办法

uniapp小程序自定义底部tabbar&#xff0c;但是在转成H5和app时发现"custom": true 无效&#xff0c;原生tabbar会显示出来 解决办法如下 在tabbar的list中设置 “visible”:false 代码如下&#xff1a;"tabBar": {"custom": true,//"cust…...

ABP-Book Store Application中文讲解 - 前期准备 - Part 2:创建Acme.BookStore + Angular

ABP-Book Store Application中文讲解-汇总-CSDN博客 因为本系列文章使用的.NET8 SDK&#xff0c;此处仅介绍如何使用abp cli .NET 8 SDK SQL sevrer 2014创建Angular模板的Acme.BookStore。 目录 1. ABP cli创建项目 1.1 打开cmd.exe 1.2 创建项目 2. ABP Studio创建项…...

基于k8s的Jenkins CI/CD平台部署实践(三):集成ArgoCD实现持续部署

基于k8s的Jenkins CI/CD平台部署实践&#xff08;三&#xff09;&#xff1a;集成ArgoCD实现持续部署 文章目录 基于k8s的Jenkins CI/CD平台部署实践&#xff08;三&#xff09;&#xff1a;集成ArgoCD实现持续部署一、Argocd简介二、安装Helm三、Helm安装ArgoCD实战1. 添加Arg…...

Starrocks 的 ShortCircuit短路径

背景 本文基于 Starrocks 3.3.5 本文主要来探索一下Starrocks在FE端怎么实现 短路径&#xff0c;从而加速点查查询速度。 在用户层级需要设置 enable_short_circuit 为true 分析 数据流&#xff1a; 直接到StatementPlanner.createQueryPlan方法&#xff1a; ... OptExpres…...

JVM——Java字节码基础

引入 Java字节码&#xff08;Java Bytecode&#xff09;是Java技术体系的核心枢纽&#xff0c;所有Java源码经过编译器处理后&#xff0c;最终都会转化为.class文件中的字节码指令。这些指令不依赖于具体的硬件架构和操作系统&#xff0c;而是由Java虚拟机&#xff08;JVM&…...

控制台打印带格式内容

1. 场景 很多软件会在控制台打印带颜色和格式的文字&#xff0c;需要使用转义符实现这个功能。 2. 详细说明 2.1.转义符说明 样式开始&#xff1a;\033[参数1;参数2;参数3m 可以多个参数叠加&#xff0c;若同一类型的参数&#xff08;如字体颜色&#xff09;设置了多个&…...

外网访问内网海康威视监控视频的方案:WebRTC + Coturn 搭建

外网访问内网海康威视监控视频的方案&#xff1a;WebRTC Coturn 需求背景 在仓库中有海康威视的监控摄像头&#xff0c;内网中是可以直接访问到监控摄像的画面&#xff0c;由于项目的需求&#xff0c;需要在外网中也能看到监控画面。 实现这个功能的意义在于远程操控设备的…...

DA14585墨水屏学习(2)

一、user_svc2_wr_ind_handler函数 void user_svc2_wr_ind_handler(ke_msg_id_t const msgid,struct custs1_val_write_ind const *param,ke_task_id_t const dest_id,ke_task_id_t const src_id) {// sprintf(buf2,"HEX %d :",param->length);arch_printf("…...

Linux系统下的延迟任务及定时任务

1、延迟任务 概念&#xff1a; 在系统中我们的维护工作大多数时在服务器行对闲置时进行 我们需要用延迟任务来解决自动进行的一次性的维护 延迟任务时一次性的&#xff0c;不会重复执行 当延迟任务产生输出后&#xff0c;这些输出会以邮件的形式发送给延迟任务发起者 在 RH…...

Spark 之 YarnCoarseGrainedExecutorBackend

YarnCoarseGrainedExecutorBackend executor ID , 在日志里也有体现。 25/05/06 12:41:58 INFO YarnCoarseGrainedExecutorBackend: Successfully registered with driver 25/05...

【网络原理】数据链路层

目录 一. 以太网 二. 以太网数据帧 三. MAC地址 四. MTU 五. ARP协议 六. DNS 一. 以太网 以太网是一种基于有线或无线介质的计算机网络技术&#xff0c;定义了物理层和数据链路层的协议&#xff0c;用于在局域网中传输数据帧。 二. 以太网数据帧 1&#xff09;目标地址 …...

相或为K(位运算)蓝桥杯(JAVA)

这个题是相或为k&#xff0c;考察相或的性质&#xff0c;用俩个数举例子&#xff0c;011001和011101后面的数不管和哪个数相或都不可能变成前面的数&#xff0c;所以利用这个性质我们可以用相与运算来把和k对应位置的1都积累起来&#xff0c;看最后能不能拼起来k如果能拼起来k那…...

AI汽车时代的全面赋能者:德赛西威全栈能力再升级

AI汽车未来智慧出行场景正在描绘出巨大的商业图景&#xff0c;德赛西威已经抢先入局。 在2025年上海车展开幕前夕&#xff0c;德赛西威发布2030年全新使命愿景——“创领安全、愉悦和绿色的出行生活”&#xff0c;并推出全栈式智慧出行解决方案Smart Solution3.0、车路云一体式…...

Python函数:从基础到进阶的完整指南

在Python编程中,函数是构建高效、可维护代码的核心工具。无论是开发Web应用、数据分析还是人工智能模型,函数都能将复杂逻辑模块化,提升代码复用率与团队协作效率。本文将从函数基础语法出发,深入探讨参数传递机制、高阶特性及最佳实践,助你掌握这一编程基石。 一、函数基…...

学习Python的第四天之网络爬虫

30岁程序员学习Python的第四天之网络爬虫的Scrapy库 Scrapy库的基本信息 Scrapy库的安装 在windows系统中通过管理员权限打开cmd。运行pip install scrapy即可安装。 通过命令scrapy -h可查看scrapy库是否安装成功. Scrapy库的基础信息 scrapy库是一种爬虫框架库 爬虫框…...

5、开放式PLC梯形图编程组件 - /自动化与控制组件/open-plc-programming

76个工业组件库示例汇总 开放式PLC编程环境 这是一个开放式PLC编程环境的自定义组件&#xff0c;提供了一个面向智能仓储堆垛机控制的开放式PLC编程环境。该组件采用苹果科技风格设计&#xff0c;支持多厂商PLC硬件&#xff0c;具有直观的界面和丰富的功能。 功能特点 多语…...

数据指标和数据标签

数据指标和数据标签是数据管理与分析中的两个重要概念&#xff0c;它们在用途、形式和应用场景上有显著区别。以下是两者的详细对比&#xff1a; 1. 核心定义 维度数据指标&#xff08;Data Metrics&#xff09;数据标签&#xff08;Data Tags/Labels&#xff09;定义量化衡量…...

linux中常用的命令(三)

目录 1- ls(查看当前目录下的内容) 2- pwd (查看当前所在的文件夹) 3- cd [目录名]&#xff08;切换文件夹&#xff09; 4- touch [文件名] &#xff08;如果文件不存在&#xff0c;新建文件&#xff09; 5- mkdir[目录名] &#xff08;创建目录&#xff09; 6-rm[文件名]&…...

Java 中 AQS 的实现原理

AQS 简介 AQS(全称AbstractQueuedSynchronizer)即抽象同步队列&#xff0c;它是实现同步器的基础组件&#xff0c;并发包中锁的底层就是使用AQS实现的。 由类图可以看到&#xff0c;AQS是一个FIFO的双向队列&#xff0c;其内部通过节点head和tail记录队首和队尾元素&#xff0…...

『Python学习笔记』ubuntu解决matplotlit中文乱码的问题!

ubuntu解决matplotlit中文乱码的问题&#xff01; 文章目录 simhei.ttf字体下载链接&#xff1a;http://xiazaiziti.com/210356.html将字体放到合适的地方 sudo cp SimHei.ttf /usr/share/fonts/(base) zkfzkf:~$ fc-list | grep -i "SimHei" /usr/local/share/font…...

docker compose ps 命令

docker compose ps 命令用于列出与 Docker Compose 项目相关的容器及其状态。 docker compose ps 能显示当前项目中所有服务容器的运行状态、端口映射等信息。 语法 docker compose ps [OPTIONS] [SERVICE…] SERVICE&#xff08;可选&#xff09;&#xff1a;指定要查看状态…...

redis数据结构-04 (HINCRBY、HDEL、HKEYS、HVALS)

哈希操作&#xff1a;HINCRBY、HDEL、HKEYS、HVALS Redis 中的哈希功能极其丰富&#xff0c;让您能够以类似于编程语言中对象的方式存储和检索数据。本课将深入探讨具体的哈希操作&#xff0c;这些操作为操作以下结构中的数据提供了强大的工具&#xff1a; HINCRBY 、 HDEL 、…...

鸿蒙知识总结

判断题 1、 在http模块中&#xff0c;多个请求可以使用同一个httpRequest对象&#xff0c;httpRequest对象可以复用。&#xff08;错误&#xff09; 2、订阅dataReceiverProgress响应事件是用来接收HTTP流式响应数据。&#xff08;错误&#xff09; 3、ArkTS中变量声明时不需要…...

Ubuntu 22虚拟机【网络故障】快速解决指南

Ubuntu22虚拟机突然无法连接网络了&#xff0c;以下是故障排除步骤记录。 Ubuntu 22虚拟机网络故障快速解决指南 当在虚拟机中安装的 Ubuntu 22 系统出现 ping: connect: 网络不可达 和 ping: www.baidu.com: 域名解析出现暂时性错误的报错时&#xff0c;通常意味着虚拟机无法…...