Java服务端开发基石:深入理解Spring IoC与依赖注入 (DI)
今天,我们从现代Java开发,尤其是企业级应用中,几乎无处不在的Spring框架的核心概念开始:控制反转(Inversion of Control, IoC) 与 依赖注入(Dependency Injection, DI)。理解它们,是掌握Spring乃至众多现代框架的基石。
一、缘起:为什么需要IoC/DI?
让我们回到没有Spring的“石器时代”。假设我们有一个OrderService(订单服务)需要使用UserRepository(用户仓库)来获取用户信息,并使用NotificationService(通知服务)来发送订单确认。传统的做法可能是这样的:
// 用户仓库接口
interface UserRepository {User findById(long id);
}// 用户仓库实现 - 数据库版本
class JdbcUserRepository implements UserRepository {@Overridepublic User findById(long id) {// ... 通过JDBC查询数据库获取用户 ...System.out.println("Finding user " + id + " via JDBC");return new User(id, "DatabaseUser");}
}// 通知服务接口
interface NotificationService {void sendNotification(User user, String message);
}// 通知服务实现 - 邮件版本
class EmailNotificationService implements NotificationService {@Overridepublic void sendNotification(User user, String message) {// ... 通过邮件API发送通知 ...System.out.println("Sending email notification to " + user.getName() + ": " + message);}
}// 订单服务
class OrderService {// OrderService *主动* 创建并持有其依赖private UserRepository userRepository = new JdbcUserRepository();private NotificationService notificationService = new EmailNotificationService();public void placeOrder(long userId, String item) {User user = userRepository.findById(userId);// ... 创建订单逻辑 ...System.out.println("Placing order for item: " + item);notificationService.sendNotification(user, "Your order for " + item + " has been placed.");}
}// --- 使用 ---
public class TraditionalApp {public static void main(String[] args) {OrderService orderService = new OrderService();orderService.placeOrder(1L, "Laptop");}
}
这种方式有什么问题?
-
紧耦合 (Tight Coupling): OrderService直接依赖于JdbcUserRepository和EmailNotificationService这两个具体的实现类。如果我想把用户仓库换成LdapUserRepository,或者通知服务换成SmsNotificationService,就必须修改OrderService的源代码。这违反了“对修改关闭,对扩展开放”的原则。
-
责任不清: OrderService不仅要负责处理订单逻辑,还要负责创建和管理它的依赖对象(userRepository, notificationService)。对象的创建和生命周期管理逻辑散布在各个业务类中。
-
测试困难: 对OrderService进行单元测试时,很难(或者需要特殊技巧)替换掉真实的JdbcUserRepository和EmailNotificationService,使得测试依赖于外部环境(如数据库、邮件服务器),导致测试不稳定且缓慢。
为了解决这些问题,控制反转(IoC) 和 依赖注入(DI) 应运而生。
二、控制反转 (Inversion of Control, IoC)
IoC是一种设计原则,它的核心思想是:将对象的创建、组装和管理(生命周期)的控制权,从应用程序代码(如OrderService内部)转移到一个外部的容器或框架(如Spring IoC容器)。
想象一下:以前是你(OrderService)需要什么工具(UserRepository, NotificationService),就得自己去造(new Xxx())。现在,你只需要告诉一个“管家”(IoC容器),你需要哪些工具,这个“管家”会负责找到或制造这些工具,并在你需要的时候提供给你。
控制权发生了反转:从你主动控制依赖的创建,变成了由外部容器控制对象的创建和关系。
Spring框架提供了强大的IoC容器(主要实现是ApplicationContext),它负责实例化、配置和组装我们应用程序中的对象(在Spring中称为Bean)。
三、依赖注入 (Dependency Injection, DI)
DI是实现IoC最常见和最主要的方式。它描述的是对象如何获取其依赖的过程。
如果说IoC是一种目标(让容器管理对象),那么DI就是达成这个目标的具体手段。DI的核心在于:一个对象所依赖的其他对象(它的依赖),不是由对象自己创建或查找,而是由外部(IoC容器)“注入”给它。
Spring支持多种DI方式:虽然字段注入在某些场景(如测试类中注入Mock对象)下很方便,但在核心业务逻辑组件中,强烈建议优先使用构造器注入。
四、Spring IoC容器的工作方式 (简化版)
-
定义Bean: 你需要告诉Spring容器哪些Java类需要被管理。可以通过XML配置文件(早期方式)或更现代的注解方式(如@Component, @Service, @Repository, @Controller, @Configuration, @Bean)来定义Bean。
import org.springframework.stereotype.Repository;@Repository // 告诉Spring这是数据访问层的Bean class JdbcUserRepository implements UserRepository { /* ... */ }import org.springframework.stereotype.Service;@Service // 告诉Spring这是服务层的Bean class EmailNotificationService implements NotificationService { /* ... */ }// OrderService 如上文使用 @Service 标记 -
容器启动与实例化: 当Spring应用启动时,IoC容器会读取这些Bean的定义(扫描带有注解的类,或解析XML)。
-
依赖解析与注入: 容器会分析Bean之间的依赖关系(例如,OrderService依赖UserRepository和NotificationService)。然后,容器会创建这些Bean的实例,并通过选定的注入方式(构造器、Setter或字段)将依赖关系注入到相应的Bean中。
-
获取与使用Bean: 应用程序代码不再直接new对象,而是向IoC容器请求所需的Bean实例。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext;@SpringBootApplication // 包含了组件扫描等配置 public class ModernApp {public static void main(String[] args) {// 启动Spring Boot应用, 它会创建并初始化ApplicationContext (IoC容器)ApplicationContext context = SpringApplication.run(ModernApp.class, args);// 从容器中获取OrderService的实例 (此时依赖已注入)OrderService orderService = context.getBean(OrderService.class);// 使用服务orderService.placeOrder(1L, "Monitor");// 演示获取其他Bean (假设它们也被正确配置和注入)UserRepository userRepository = context.getBean(UserRepository.class);User user = userRepository.findById(2L);System.out.println("Found user: " + user.getName());} } // 需要在项目中添加Spring Boot依赖 // 并且确保 User, UserRepository, NotificationService 接口和实现类在扫描路径下注意: 在实际的Spring Boot应用中,我们通常不会直接从main方法获取Bean,而是通过进一步的依赖注入将Bean注入到Controller、Service等其他组件中。
五、IoC/DI带来的好处
-
解耦 (Decoupling): 组件之间依赖接口而非具体实现,更换实现变得容易,只需修改配置或添加新的Bean定义,无需修改依赖方的代码。
-
易于测试 (Testability): 可以轻松地为OrderService注入Mock的UserRepository和NotificationService实现,进行隔离的单元测试。
-
代码更简洁: 业务组件专注于核心逻辑,对象的创建、配置和生命周期管理交由容器负责。
-
可维护性和可重用性: 松耦合的设计使得系统更容易维护和扩展,组件也更容易在不同场景下复用。
-
集中管理: 对象的配置和依赖关系集中在容器(通过注解或XML)中管理,更清晰。
六、总结
控制反转(IoC)是一种重要的设计原则,它将对象创建和管理的控制权交给外部容器。依赖注入(DI)是实现IoC的主要方式,即对象的依赖由外部容器动态注入。Spring框架通过其强大的IoC容器,极大地简化了Java应用的开发,促进了松耦合、可测试、可维护的设计。
掌握IoC和DI是理解Spring及其生态(如Spring Boot, Spring Cloud)运作方式的基础。虽然它们是“老”概念,但在现代Java服务端开发中依然是核心中的核心。
相关文章:
Java服务端开发基石:深入理解Spring IoC与依赖注入 (DI)
今天,我们从现代Java开发,尤其是企业级应用中,几乎无处不在的Spring框架的核心概念开始:控制反转(Inversion of Control, IoC) 与 依赖注入(Dependency Injection, DI)。理解它们&am…...
【人工智能】大语言模型多义词解析技术揭秘——以“项目“歧义消解为例
今天田辛老师和小伙伴探讨了一个有趣的多义词问题, 在人工智能技术日新月异的今天,大语言模型(LLM)对自然语言的理解能力已经达到令人惊叹的水平。大模型到底是如何去区分多义词的? 比如:当用户提到"…...
贪心算法(17)(java)可被三整除的最大整数和
给你一个整数数组 nums,请你找出并返回能被三整除的元素 最大和。 示例 1: 输入:nums [3,6,5,1,8] 输出:18 解释:选出数字 3, 6, 1 和 8,它们的和是 18(可被 3 整除的最大和)。 …...
qq邮箱群发程序
1.界面设计 1.1 环境配置 在外部工具位置进行配置 1.2 UI界面设计 1.2.1 进入QT的UI设计界面 在pycharm中按顺序点击,进入UI编辑界面: 点击第三步后进入QT的UI设计界面,通过点击按钮进行界面设计,设计后进行保存到当前Pycharm…...
K8S学习之基础七十九:关闭istio功能
关闭istio功能 kubectl get ns --show-labels kubectl label ns default istio-injection-有istio-injectionenabled的命名空间,pod都会开启istio功能 反之,如果要开启istio,在对应命名空间打上该标签即可...
上门预约洗鞋店小程序都具备哪些功能?
现在大家对洗鞋子的清洗条件越来越高,在家里不想去,那就要拿去洗鞋店去洗。如果有的客户没时间去洗鞋店,这个时候,有个洗鞋店小程序就可以进行上门取件,帮助没时间的客户去取需要清洗的鞋子,这样岂不是既帮…...
在Ubuntu 22.04上配置【C/C++编译环境】
在Ubuntu 22.04上配置C/C编译环境 如果你想在Ubuntu 22.04上编译和运行C或C程序,首先需要安装一个合适的编译器和相关工具。本文将为你提供详细的安装建议和操作步骤,帮助你快速搭建开发环境。 准备工作 在开始之前,确保你的系统可以通过终…...
蓝桥杯——走迷宫(Java-BFS)
这是一个经典的BFS算法 1. BFS算法保证最短路径 核心机制:广度优先搜索按层遍历所有可能的路径,首次到达终点的路径长度即为最短步数。这是BFS的核心优势。队列的作用:通过队列按先进先出的顺序处理节点,确保每一步探索的都是当…...
Spring MVC与Spring Boot文件上传配置差异对比及文件上传关键类详细说明与对比
一、Spring MVC与Spring Boot文件上传配置差异对比 1. 配置方式差异 框架配置方式依赖管理自动配置Spring MVC需手动配置MultipartResolver(如StandardServletMultipartResolver)需自行引入commons-fileupload等依赖无,默认不启用文件上传支…...
LLM 的model.generate() 参数说明
LLM 的model.generate() 参数说明 目录 LLM 的model.generate() 参数说明生成长度控制参数采样策略参数重复惩罚参数束搜索参数其他参数model.generate() 方法是 Hugging Face Transformers 库中用于文本生成的核心方法,它有众多参数可用于控制生成过程 生成长度控制参数 min…...
下载firefox.tar.xz后如何将其加入到Gnome启动器
起因:近期(2025-04-07)发现firefox公布了130.0 版本,可以对pdf文档进行签名了,想试一下,所以卸载了我的Debian12上的firefox-esr,直接下载了新版本的tar.xz 包。 经过一番摸索,实现了将其加入Gn…...
Flutter性能优化终极指南:从JIT到AOT的深度调优
一、Impeller渲染引擎调优策略 1.1 JIT预热智能预编译 // 配置Impeller预编译策略 void configureImpeller() {ImpellerEngine.precacheShaders(shaders: [lib/shaders/skinned_mesh.vert,lib/shaders/particle_system.frag],warmupFrames: 30, // 首屏渲染前预编译帧数cach…...
加密≠安全:文件夹密码遗忘背后的数据丢失风险与应对
在数字化时代,保护个人隐私和数据安全变得尤为重要。许多人选择对重要文件夹进行加密,以防止未经授权的访问。然而,一个常见且令人头疼的问题也随之而来——文件夹加密密码遗忘。当你突然发现自己无法访问那些加密的文件夹时,那种…...
实习技能记录【2】-----LVGL[基本概念]
LVGL主要概念 1. Screen (屏幕): 概念: 屏幕是 LVGL 应用程序中的顶层容器。它是用户界面的根对象,所有的可见 UI 元素最终都会添加到某个屏幕上(通常是活动屏幕)。 功能: 作为其他 UI 元素的父对象。 可以拥有自己的背景颜色、背景图片等样…...
【操作系统(Linux)】——通过案例学习父子进程的线程异步性
本篇旨在通过几个案例来学习父子进程的线程异步性 一、父进程与子进程 我们将要做的: 创建父子进程,观察父子进程执行的顺序,了解进程执行的异步行为 源代码: #include <stdio.h> #include <sys/types.h> #include…...
Go 语言范围 (Range)
Go 语言范围 (Range) Go 语言是一种静态强类型、编译型、并发型编程语言,由 Google 开发。它的简洁性和高效性使其成为众多开发者的首选。在 Go 语言中,range 是一个非常有用的关键字,用于遍历数组、切片、字符串以及通道(channe…...
【开源宝藏】30天学会CSS - DAY12 第十二课 从左向右填充的文字标题动画
用伪元素搞定文字填充动效:一行 JS 不写,效果炸裂 你是否曾经在设计页面标题时,觉得纯文字太寡淡?或者想做一个有动感的文字特效,但又不想引入 JS 甚至 SVG? 在这篇文章中,我们将通过 一段不到…...
nginx或tengine服务器,配置HTTPS下使用WebSocket的线上环境实践!
问题描述: HTTPS 下发起WS连接,连接失败,Chrom 浏览器报错。 socket.js:19 Mixed Content: The page at https://app.XXX.com was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint ws://172.16.10.80:903…...
WSA(Windows 安卓子系统)过检测教程
windows安卓子系统WSA的root和magisk的安装教程 安卓子系统WSLWSA的rootmagisk安装 WSA(Windows 安卓子系统)过检测的方法与思路 一、引言 Windows 安卓子系统(WSA)为 Windows 用户提供了在电脑上运行安卓应用的便利。然而&…...
蓝桥杯 B3620 x 进制转 10 进制
题目描述 给一个小整数 x 和一个 x 进制的数 S。将 S 转为 10 进制数。对于超过十进制的数码,用 A,B,… 表示。 输入格式 第一行一个整数 x; 第二行一个字符串 S。 输出格式 输出仅包含一个整数,表示答案。 输入输出样例 …...
【Oracle篇】跨字符集迁移:基于数据泵的ZHS16GBK转AL32UTF8全流程迁移
💫《博主主页》:奈斯DB-CSDN博客 🔥《擅长领域》:擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控;并对SQLserver、NoSQL(MongoDB)有了解 💖如果觉得文章对你有所帮…...
Qt子模块的功能介绍
一、Qt 主要子模块的功能介绍 1. 核心模块 模块名称功能描述QtCore核心非GUI功能(信号槽、线程、文件IO、容器类、JSON/XML处理等)QtGui基础图形绘制(窗口系统集成、OpenGL抽象、图像处理、字体管理等)QtConcurrent高级多线程API(并行计算框架,如QtConcurrent::run)QtN…...
FRP练手:hello,world实现
方案一:使用 Flask(推荐) from flask import Flaskapp Flask(__name__)app.route(/) def hello_world():return "你好啊世界"if __name__ __main__:# 监听所有网络接口(0.0.0.0),端口 3344app.…...
《深入探秘:分布式软总线自发现、自组网技术原理》
在当今数字化浪潮中,分布式系统的发展日新月异,而分布式软总线作为实现设备高效互联的关键技术,其自发现与自组网功能宛如打开智能世界大门的钥匙,为多设备协同工作奠定了坚实基础。 分布式软总线的重要地位 分布式软总线是构建…...
西门子S7-1200PLC 工艺指令PID_Temp进行控温
1.硬件需求: 西门子PLC:CPU 1215C DC/DC/DC PLC模块:SM 1231 TC模块 个人电脑:已安装TIA Portal V17软件 加热套:带加热电源线以及K型热电偶插头 固态继电器:恩爵 RT-SSK4A2032-08S-F 其他࿱…...
提升Windows安全的一些措施
由简单到复杂,仅供参考 一、杀毒软件: 1、杀毒能力: https://haokan.hao123.com/v?vid3883775443252827335&pdhaokan_share 2、使用注意: 一台主机只安装一个杀毒软件就可以了 杀毒软件会误报,造成正常文件…...
Jupyter notebook定制字体
一、生成配置文件 运行Anaconda Powershell Prompt终端,输入下面一行代码: jupyter notebook --generate-config 将生成文件“C:\Users\XXX\.jupyter\jupyter_notebook_config.py”,XXX为计算机账户名字。 二、修改配置文件 c.NotebookAp…...
内存分配中的堆(Memory Heap)详解
在计算机科学中,"堆"这个术语确实容易让人混淆,因为它同时用于描述两种完全不同的概念:数据结构中的堆和内存管理中的堆。上次我们讨论了数据结构中的堆,今天我将详细解释内存分配中的堆(Memory Heap&#x…...
vant4+vue3上传一个pdf文件并实现pdf的预览。使用插件pdf.js
注意下载的插件的版本"pdfjs-dist": "^2.2.228", npm i pdfjs-dist2.2.228 然后封装一个pdf的遮罩。因为pdf文件有多页,所以我用了swiper轮播的形式展示。因为用到移动端,手动滑动页面这样比点下一页下一页的方便多了。 直接贴代码…...
JS | 函数柯里化
函数柯里化(Currying):将一个接收多个参数函数,转换为一系列只接受一个参数的函数的过程。即 逐个接收参数。 例子: 普通函数: function add(a, b, c) {return a b c; } add(1, 2, 3); // 输出 6柯里化…...
