跳到主要内容
Spring IoC 与 DI 核心概念及实战用法 | 极客日志
Java java
Spring IoC 与 DI 核心概念及实战用法 综述由AI生成 Spring IoC(控制反转)与 DI(依赖注入)是 Spring 框架的核心机制,旨在实现对象解耦。IoC 将对象创建权交给容器,DI 通过属性、构造方法或 Setter 方式注入依赖。了 Bean 的注册(类注解如@Component/@Service,方法注解@Bean),扫描路径配置,以及多 Bean 注入冲突的解决方案(@Primary/@Qualifier/@Resource)。掌握这些基础是理解 Spring AOP、事务管理等高级特性的前提。
独立开发者 发布于 2026/3/30 更新于 2026/5/23 28 浏览一、IOC&DI 介绍
1. 传统程序开发的问题:高耦合
以'造一辆车'为例,传统开发中对象的创建和依赖关系由自身控制:汽车依赖车身,车身依赖底盘,底盘依赖轮子。
所有的对象都通过 new 手动创建;当底层组件 (如轮胎尺寸) 发生变化时,整个调用链上的所有代码都需要修改,程序耦合度高,可维护性差
public class Main {
public static void main (String[] args) {
Car car = new Car (21 );
car.run();
}
}
public class Bottom {
private Tire tire;
public Bottom (Integer size) {
this .tire = new Tire (size);
System.out.println("bottom init..." );
}
}
public class Car {
private Framework framework;
public Car (Integer size) {
this .framework = new Framework (size);
System.out.println("car init..." );
}
public void run {
System.out.println( );
}
}
{
Bottom bottom;
{
.bottom = (size);
System.out.println( );
}
}
{
size;
{
.size = size;
System.out.println( + size);
}
}
()
"car run..."
public
class
Framework
private
public
Framework
(Integer size)
this
new
Bottom
"framework init..."
public
class
Tire
int
public
Tire
(Integer size)
this
"tire init, size:"
随着车辆的个性化需求增多,如果我们修改代码,会发现修改成本很高。
从上述代码可以看出,问题出现在:当最底层代码改动后,整个调用链上的所有代码都需要修改。在上面的程序中,我们是根据轮子的尺寸设计的底盘,轮子的尺寸一改,底盘的设计就得修改。同样因为我们是根据底盘设计的车身,那么车身也得改,同理汽车设计也得改,也就是整个设计几乎都得改。
2. IoC (Inversion of Control,控制反转) Spring 是一个'控制反转'的容器,本质是对象的创建权由程序自身反转给 Spring 容器。
传统模式 :程序需要主动通过 new 关键字创建对象
IoC 模式 :对象的创建和管理交给 IoC 容器,程序只需从容器中获取对象即可
IoC 的核心价值 :实现程序解耦,将对象之间的依赖关系从代码中剥离,由容器统一管理,底层组件变化时,上层代码无需修改
举一个例子直观理解 IoC 思想:自动驾驶传统驾驶:车辆的控制权由驾驶员掌握;自动驾驶:控制权反转,交给驾驶自动化系统处理。
3. 解决方式:DI (Dependency Injection,依赖注入) 它是实现 IoC 的主要方式 ,Spring 容器在创建对象时,容器会动态地为程序提供运行时所以来的资源 (对象)。
关系 : Ioc 是思想/目标,DI 是现实手段,二者在不同的角度描述这同一间事情——解耦对象依赖
举个例子:理解 IOC 和 DI 之间的关系'想吃个好的'(Ioc 思想),选择'吃火锅'或者'吃烤肉'(DI 具体实现),思想指导实现,实现落地思想。
容器在运行期间,动态的为应用程序提供运行时依赖的资源
我们尝试换一种思路,先设计车身,根据车身来设计底盘,根据底盘来设计轮子;得到依赖关系:轮子依赖底盘,底盘依赖车身,车身依赖汽车。
只需要将原本由自己创建的下级类,改为**传递 (注入)**的方式。
通过构造函数的方式,把依赖对象注入到需要使用的对象中。
public class main {
public static void main (String[] args) {
Tire tire = new Tire (2 ,"red" );
Bottom bottom = new Bottom (tire);
Framework framework = new Framework (bottom);
Car car = new Car (framework);
car.run();
}
}
public class Tire {
int size;
String color;
public Tire (Integer size, String color) {
this .color = color;
this .size = size;
System.out.println("tire init:" + color + size);
}
}
public class Bottom {
private Tire tire;
public Bottom (Tire Tire) {
this .tire = tire;
System.out.println("bottom init...." );
}
}
public class Framework {
private Bottom bottom;
public Framework (Bottom bottom) {
this .bottom = bottom;
System.out.println("Framework init...." );
}
}
public class Car {
private Framework framework;
public Car (Framework framework) {
this .framework = framework;
System.out.println("car init..." );
}
public void run () {
System.out.println("car run..." );
}
}
通过上述调整:无论底层如何变化,整个调用链不做任何变化,这样就完成了代码的解耦。
4. IoC 容器的核心能力
Spring 作为 IoC 容器,核心只做两件事:存:将对象 (Bean) 交给 Spring 容器管理;取:程序需要时,从 Spring 容器中获取依赖的 Bean 对象。
二、IoC 实战:Bean 的存储 (将对象交给 Spring 管理) 将对象交给 Spring 容器管理,即 Bean 的注册,Spring 提供类注解和方法注解两种方式,其中类注解是日常开发的主流,方法注解用于特殊场景。
1. 类注解 注释
对应分层
核心作用
@Controller
控制层 (Web)
接收请求,处理请求,响应结果
@Service
业务逻辑层 (Service)
处理具体的业务逻辑
@Repository
数据访问层 (Dao)
负责数据库/数据源操作
@Configuration
配置层
处理项目的配置信息
@Component
通用组件层
非分层的通用组件注册
使用规则 : 直接将注解加到类上即可,Spring 启动会自动扫描并将该实体类实例化作为 Bean,纳入容器管理。
为什么有多个类注解:可以让开发者看到注解就能直接判断出类的用途,符合后端分层开发的规范,提升代码可读性。
1.1 @Component(通用注解) 核心作用 : 最基础的注解,告诉 Spring'这个类需要被 IoC 容器实例化并管理',是其他 4 个注解的'父注解'(其它注解本质上就是对@Controller 的特殊化)。
使用场景 : 当你的类不属于 Controller/Service/Repository/Configuration 任何一层,又需要被 Spring 管理时使用 (如工具,组件)。
package com.boop.springioc.TestNode.Component;
import org.springframework.stereotype.Component;
@Component
public class UserComponent {
public void print () {
System.out.println("do Component" );
}
}
package com.boop.springioc;
import com.boop.springioc.TestNode.Component.UserComponent;
import com.boop.springioc.TestNode.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class SpringIocApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
UserComponent userComponent = context.getBean(UserComponent.class);
userComponent.print();
}
}
① 如果手动删除@Coponent
报错信息 : 找不到类型是'com.boop.springioc.TestNode.Component.UserComponent'的 bean
② ApplicationContext 获取 bean 对象的功能,是父类 BeanFactory 提供的
③ 默认 bean 名称:根据 Bean 的命名规则,来手动获取 Bean
1.2 @Controller(表现层注解) 核心作用 : 标记类为 SpringMVC 的控制器 (处理 HTTP 请求),并且是 Web 层的组件。
配合@RequestMapping 等注解处理特殊请求
Spring MVC 的异常处理器,参数绑定等功能会优先识别该类注解标记的类
本质:@Controller = @Component + Web 层语义
package com.boop.springioc.TestNode.Controller;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@RequestMapping("/usctrl")
@ResponseBody
@Controller
public class UserController {
@RequestMapping("/sayHi")
public void sayHi () {
System.out.println("hi,UserController..." );
}
}
package com.boop.springioc;
import com.boop.springioc.TestNode.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class SpringIocApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
UserController userController = context.getBean(UserController.class);
userController.sayHi();
}
}
① Spring 上下文对象:Spring 上下文对象 (ApplicationContext):本质是:Spring 框架的核心容器 + 运行上下文环境,是 Spring 整个应用的'总控室'。它继承了多个核心接口,具备:IoC 容器核心,环境与配置管理,资源加载器,事件发布/监听,国际化支持,应用层上下文整合。
② 为什么还要加 @ResponseBody:如果只加 @Controller 时,Spring 会把返回值当作视图名去寻找模板 (如 xxx.html);示例中的方法 sayHI() 返回值是 void,如果要直接输出字符串/JSON 给前端,必须告诉 Spring 这是响应体,而不是页面;此时就需要用到 @ResponseBody,加在类上,并且表示类的所有接口都直接返回数据,不找视图。我还想让方法 sayHI() 成为一个可以访问的 HTTP 接口 ,就必须加@ResponseBody,@RequestMapping 等注解;如果方法 sayHI() 只是在内部调用,不对外提供接口,那就不需要加@ResponseBody 。
1.3 @Service(业务层注解) 核心作用 : 标记类为业务逻辑层组件,语义上明确这是'处理核心业务逻辑'的类。
语义化强,便于团队协作和代码维护
本质:@Service = @Component + 业务层语义
package com.boop.springioc.TestNode.Service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void print () {
System.out.println("do Service" );
}
}
import ...
@SpringBootApplication
public class SpringIocApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
UserService userService = context.getBean(UserService.class);
userService.print();
}
}
1.4 @Repository(数据访问层注解) 核心作用 : 标记类为数据访问层 (Dao) 组件,负责与数据库/数据源交互。
自动转换 JDBC 相关的异常 (将原生 SQL 异常转化为 SPring 统一的 DataAccessException)
语义上明确这是'数据访问层',是持久化操作的核心
本质:@Repository = @Component + 数据访问层语义 + 异常转换
package com.boop.springioc.TestNode.Respository;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public void print () {
System.out.println("do Respository" );
}
}
@SpringBootApplication
public class SpringIocApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
UserRepository userRepository = context.getBean(UserRepository.class);
userRepository.print();
}
}
1.5 @Configuration(配置类注解) 核心作用 : 标记类为 Spring 的配置类,替代传统的 XML 配置文件,用于定义 Bean,配置依赖等。
配合@Bean 注释可以手动注册 Bean(方法即注解)
配置类本身也是一个 Bean,但优先级高于普通 @Component
支持@ComponentScan(扫描指定包下的注解),@Import(导入其他配置类) 等
本质:@Configuration = @Component + 配置类语义 + 增强的 Bean 定义能力
package com.boop.springioc.TestNode.Config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
public void print () {
System.out.println("do config" );
}
}
@SpringBootApplication
public class SpringIocApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
UserConfig userConfig = context.getBean(UserConfig.class);
userConfig.print();
}
}
同样的,删除@Configuration,也会报错。
2. 方法注解 类注解适用于自定义类的 Bean 注册,但遇到外部包的类 (如第三方工具类) 或一个类需要多个实例 (如多数据源) 时,类注解无法使用,此时就需要 @Bean 注解。
2.1 @Bean @Bean 是方法注解,必须配合类注解使用 (如@Component,@Configuration),通过方法返回值将对象注册为 Bean,默认 Bean 名称为方法名。
package com.boop.springioc.TestNode.model;
import lombok.Data;
@Data
public class User {
private String name;
private Integer age;
}
package com.boop.springioc.TestNode.Config;
import com.boop.springioc.TestNode.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
@Bean
public User user () {
User user = new User ();
user.setName("zhangsan" );
user.setAge(12 );
return user;
}
}
① @Bean 注解一定要配合类注解使用
在 Spring 框架设计中,方法注解@Bean 要配合类注解才能将对象正常的存储到 Spring 容器中。
如果不加类注解:
② 如果同一个类,定义多个对象
@Configuration
public class UserConfig {
@Bean
public User user () {
User user = new User ();
user.setName("zhangsan" );
user.setAge(12 );
return user;
}
@Bean
public User user2 () {
User user = new User ();
user.setName("lisi" );
user.setAge(118 );
return user;
}
}
异常解析 : NoUniqueBeanDefinitionException;这是 Spring 容器中同类型 Bean 不唯一时抛出的异常。
2.2 @Bean 多个实例时,根据名称来获取对象 @SpringBootApplication
public class SpringIocApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
User user1 = (User) context.getBean("user" );
System.out.println(user1);
User user2 = (User)context.getBean("user2" );
System.out.println(user2);
}
}
2.3 重命名 Bean (通过@Bean(name = "别名")) (通过@Bean(name = "别名")) 为 Bean 设置自定义名称,支持多个别名。
@SpringBootApplication
public class SpringIocApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
User u1 = (User)context.getBean("u1" );
User user1 = (User)context.getBean("user1" );
System.out.println(u1);
System.out.println(user1);
User u2 = (User)context.getBean("u2" );
User user2 = (User)context.getBean("user2" );
System.out.println(u2);
System.out.println(user2);
}
}
@Configuration
public class UserConfig {
@Bean(name = {"u1", "user1"})
public User user () {
User user = new User ();
user.setName("zhangsan" );
user.setAge(12 );
return user;
}
@Bean(name = {"u2", "user2"})
public User user2 () {
User user = new User ();
user.setName("lisi" );
user.setAge(118 );
return user;
}
}
3. Bean 的扫描路径 使用类注解/方法注解的 Bean,并非一定能被 Spring 管理,前提是:Bean 所在的包必须能被 Spring 扫描到。
3.1 默认扫描路径 Spring Boot 的启动类注解 @SpringBootApplication 中内置了@ComponentScan,默认扫描范围是:启动类所在包及其所有子类。
3.2 手动配置扫描路径 @ComponentScan("com.boop.springioc.Test")
@SpringBootApplication
public class SpringIocApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
Test1 test = context.getBean(Test1.class);
test.print();
}
}
注意 :
① 被扫描类也需要写@Component 等注释
② 只需要写到被扫描类的完整包名即可
③ 开发推荐:将启动类放在项目的根包下 (如 com.example.demo),将所有业务作为子包,无需手动配置扫描路径,简化开发
4. Bean 的命名规则
4.1 五大类注解的 Bean 命名
默认:类名首字母小写 (如 UserController->userController)
特殊:类名前两位均大写时,保留原类名 (如 UCtroller->UCtroller)
自定义:通过注解的 value 属性设置 (如@Controller("user"))
4.2 @Bean 注解的 Bean 命名
默认:类名首字母小写
自定义:通过@Bean(name = "别名") 设置
三、DI 实战:Bean 的获取 (依赖注入的三种方式) 依赖注入是指从 Spring 容器中获取 Bean,并注入到需要依赖它的对象中,Spring 提供了属性注入,构造方法注入,Setter 注入 三种方式。
1. 属性注入 (常用) 将 @Autowired 注解直接标注在类的属性上。
@Service
public class UserService {
public void print () {
System.out.println("do Service" );
}
}
@Configuration
public class UserConfig {
public void print () {
System.out.println("do config" );
}
}
@Controller
public class HController {
@Autowired
private UserService us;
@Autowired
private UserConfig userConfig;
public void print () {
System.out.println("注入测试...." );
System.out.println(us);
us.print();
userConfig.print();
}
}
@SpringBootApplication
public class SpringIocApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
HController hController = context.getBean(HController.class);
HController hController1 = (HController) context.getBean("HController" );
hController.print();
System.out.println();
hController1.print();
}
}
异常解析: 如果去掉其中一个@Autowired , NullPointerException;
Hctroller 类中的 us 字段为 null,调用它的 print() 方法时触发了空指针异常;异常的本质时 Spring 依赖注入失败。
2. 构造方法注入 特点 : 支持注入 final 修饰的属性,依赖对象在使用前一定会被完全初始化号,通用性好。
@Controller
public class HController {
private UserService us;
private UserConfig userConfig;
@Autowired
public HController (UserConfig uc, UserService us) {
this .us = us;
this .userConfig = uc;
}
public void print () {
System.out.println("注入测试...." );
System.out.println(us);
us.print();
userConfig.print();
}
}
如果类只有一个构造方法,那么@Autowired 注解可以省略;如果类中有多个构造方法,那么需要添加上@Autowired 来明确到底使用哪个构造方法。
异常解析:
① 这个类有多个构造方法,且没有被@Autowired 标记
② 如果提供多个构造方法,且均被@Autowired 标记,服务会启动失败;原因:Spring 要求,一个类最多只能由一个构造方法被@Autowired 标记 (无论 required 是 true 还是 false),否则就会抛异常
解决方法 : 只提供一个构造方法,只给一个构造方法添加@Autowired 注解
3. Setter 注入 (灵活) 将@Autowired 注解标注在 Setter 方法上。
特点 : 支持在对象实例化后,动态修改/重新注入依赖对象,灵活。
@Controller
public class HController {
private UserService us;
private UserConfig userConfig;
@Autowired
public void setUs (UserService us) {
this .us = us;
}
@Autowired
public void setUserConfig (UserConfig userConfig) {
this .userConfig = userConfig;
}
public void print () {
System.out.println("注入测试...." );
System.out.println(us);
us.print();
userConfig.print();
}
}
异常解析:
① 如果不加@Autowired,同样报 NullPointerException
② 如果只在部分 Setter 方法上加@Autowired,只有部分对象注入成功
4. 三种注入的优缺点 注入方式
优点
缺点
属性注入
代码简洁、开发效率高
仅适用于 IoC 容器,无法注入 final 属性,可能出现 NPE
构造方法注入
支持 final 属性,初始化安全,通用性好
注入多个 Bean 时,代码繁琐
Setter 注入
支持动态修改依赖对象
无法注入 final 属性,对象可能被多次修改
5. DI 进阶:解决同一个类型多个 Bean 的注入问题 当 Spring 容器中存在同一类型的多个 Bean 时,直接使用 @Autowired 注入会抛出NoUniqueBeanDefinitionException (非唯一 Bean 异常),Spring 提供了 3 种解决方案,是核心面试考点 。
5.1 @Primary:指定默认 Bean (配合 @Bean 使用) 在其中一个 Bean 上添加 @Primary 注解,表示该 Bean 是同类型的默认 Bean,@Autowired 注入时,会优先选择该 Bean。
@Configuration
public class UserConfig {
@Primary
@Bean(name = {"u1", "user1"})
public User user () {
User user = new User ();
user.setName("zhangsan" );
user.setAge(12 );
return user;
}
@Bean(name = {"u2", "user2"})
public User user2 () {
User user = new User ();
user.setName("lisi" );
user.setAge(118 );
return user;
}
public void print () {
System.out.println("do config" );
}
}
@Controller
public class HController {
@Autowired
private User us;
@Autowired
private UserConfig userConfig;
public void print () {
System.out.println("注入测试...." );
System.out.println(us);
us.toString();
userConfig.print();
}
}
@SpringBootApplication
public class SpringIocApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
HController hController2 = context.getBean(HController.class);
hController2.print();
System.out.println();
}
}
5.2 @Qualifier:指定 Bean 名称注入 (配合 @Autowired 使用) @Qualifier 注解 必须 配合 @Autowired 使用,通过value属性指定要注入的 Bean 名称 ,精准定位目标 Bean,解决同类型多 Bean 的歧义问题。
注意 : @Qualifier 的优先级高于@Primary
@Controller
public class HController {
@Qualifier("u2")
@Autowired
private User us;
@Autowired
private UserConfig userConfig;
public void print () {
System.out.println("注入测试...." );
System.out.println(us);
us.toString();
userConfig.print();
}
}
5.3 @Resource(非 Spring 注解) @Resource 注解 是JDK 原生注解 (非 Spring 提供),默认按 Bean 名称注入 ,通过name属性指定目标 Bean 名称,无需配合其他注解,直接替代 @Autowired+@Qualifier。
注意 : @Resource 优先级最高 ;@Resource(name = "xxx") > @Qualifier("xxx") > @Primary > 类型匹配。
@Controller
public class HController {
@Resource(name = "u2")
private User us;
@Autowired
private UserConfig userConfig;
public void print () {
System.out.println("注入测试...." );
System.out.println(us);
us.toString();
userConfig.print();
}
}
5.4 @Autowired vs @Resource
所属框架不同:@Autowired 是 Spring 框架提供的;@Resource 是 JDK 原生的注解
@Autowired 默认按类型注入,失败时按名称匹配;@Resource 默认按名称注入,失败时按类型匹配
@Autowired 只有 required 属性;@Resouce 有 name,type 等多个属性
四、总结 Spring 的核心是IoC 容器 ,IoC 是思想,DI 是具体实现,二者的核心目标是实现程序解耦 。
IoC :将对象的创建权从程序反转给 Spring 容器,容器负责 Bean 的注册和管理,核心是Bean 的存储 ;
DI :容器为程序动态注入依赖的 Bean,核心是Bean 的获取 ,有属性注入、构造方法注入、Setter 注入三种方式;
Bean 注册 :五大类注解(@Controller/@Service/@Repository/@Configuration/@Component)用于自定义类,@Bean 注解用于外部包类或多实例场景,需保证 Bean 在扫描路径内;
多 Bean 注入解决 :@Primary 指定默认 Bean,@Qualifier+@Autowired 按名称注入,@Resource 原生按名称注入;
三者关系 :Spring 是 IoC 容器基础,Spring MVC 是 Spring 的 Web 子框架,Spring Boot 是 Spring 的快速开发脚手架,核心能力均基于 IoC&DI。
IoC&DI 是 Spring 体系的入门基础,也是后续学习 AOP、事务管理、Spring Cloud 等内容的前提,掌握其核心思想和实战用法,才能真正理解 Spring 的设计精髓,写出优雅的企业级 Java 代码。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online