一、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("car run...");
}
}
public class Framework {
private Bottom bottom;
public Framework(Integer size) {
this.bottom = new Bottom(size);
System.out.println("framework init...");
}
}
public class Tire {
int size;
public Tire(Integer size) {
this.size = size;
System.out.println("tire init, size:" + size);
}
}
注意: 上述代码分为 5 个类
随着车辆的个性化需求增多,如果我们修改代码,会发现修改成本很高。
从上述代码可以看出,问题出现在:当最底层代码改动后,整个调用链上的所有代码都需要修改。在上面的程序中,我们是根据轮子的尺寸设计的底盘,轮子的尺寸一改,底盘的设计就得修改。同样因为我们是根据底盘设计的车身,那么车身也得改,同理汽车设计也得改,也就是整个设计几乎都得改。
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 {
Bottom bottom;
{
.bottom = bottom;
System.out.println();
}
}
{
Framework framework;
{
.framework = framework;
System.out.println();
}
{
System.out.println();
}
}
通过上述调整:无论底层如何变化,整个调用链不做任何变化,这样就完成了代码的解耦。

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();
}
}

省去@Service 同样报错。

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();
}
}

删掉@Repository 同样报错。
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 的命名规则
Spring 会为每个 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. 构造方法注入
将@Autowired 标注在构造方法上。
特点: 支持注入 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 代码。