一文吃透 Spring 生态核心:IoC/DI 原理、Bean 管理与实战指南
目录
一、Spring、Spring MVC 与 Spring Boot:关系与区别
(3)通过类型 + 名称获取(解决同类型多个 Bean 冲突)
3. 上下文对比:ApplicationContext vs BeanFactory
1. 类注解(@Controller、@Service 等)的 Bean 命名
4. Spring、Spring MVC、Spring Boot 的关系
5. BeanFactory 与 ApplicationContext 的区别
前言
在 Java 后端开发中,Spring 生态无疑是绕不开的核心技术栈。无论是初入职场的新手,还是经验丰富的开发者,理清 Spring、Spring MVC、Spring Boot 的关系,掌握 IoC/DI 核心思想,熟练运用 Bean 管理与依赖注入,都是进阶路上的必备技能。本文将结合实战案例,从概念到实操,全面拆解 Spring 生态的核心知识点,帮你构建完整的知识体系。
一、Spring、Spring MVC 与 Spring Boot:关系与区别
很多开发者刚接触 Spring 生态时,都会被这三个框架的关系搞晕。其实可以用一个通俗的比喻理解:Spring 是一个 “大家族”,Spring MVC 是家族中负责 “Web 业务” 的成员,而 Spring Boot 是家族的 “快速启动工具”。
1. 核心定位与功能
| 框架 | 核心定位 | 核心功能 |
|---|---|---|
| Spring | 轻量级、一站式企业级应用开发框架 | 核心是 IoC 容器(管理对象及依赖)、AOP(面向切面编程)、事务管理、数据访问支持 |
| Spring MVC | 基于 Spring 的 Web 框架 | 处理 HTTP 请求、URL 路由映射、参数接收、视图渲染、接口开发(RESTful) |
| Spring Boot | Spring 生态的 “脚手架”,简化 Spring 应用开发 | 自动配置、 Starter 依赖整合、内嵌服务器(无需单独部署 Tomcat)、简化配置文件 |
2. 三者的依赖关系
- Spring 是基础:Spring MVC 和 Spring Boot 都基于 Spring 核心(IoC 容器、AOP 等)实现,无法脱离 Spring 独立存在。
- Spring MVC 是 Spring 的子模块:专门解决 Web 开发场景的问题,相当于 Spring 生态中的 “Web 模块”。
- Spring Boot 是 Spring 的 “封装增强”:不提供新功能,而是通过 “自动配置” 和 “依赖整合”,简化 Spring + Spring MVC 项目的搭建和配置。
3. 实战场景举例(图书管理系统)
// Spring Boot:搭建项目框架(自动配置 Tomcat、Spring 容器)
@SpringBootApplication
public class BookApplication {
public static void main(String[] args) {
SpringApplication.run(BookApplication.class, args);
}
}
// Spring MVC:处理 Web 请求(URL 映射、接口响应)
@RestController
@RequestMapping("/book")
public class BookController {
// Spring:IoC 容器注入依赖(BookService 对象由 Spring 管理)
@Autowired
private BookService bookService;
// Spring MVC:处理 GET 请求,返回 JSON 数据
@RequestMapping("/getList")
public List<BookInfo> getList() {
return bookService.getBookList(); // 调用业务逻辑
}
}
// Spring:管理业务层 Bean,处理核心逻辑
@Service
public class BookService {
@Autowired
private BookDao bookDao; // 依赖注入数据访问层对象
public List<BookInfo> getBookList() {
List<BookInfo> books = bookDao.mockData();
// 业务处理:转换状态为中文
for (BookInfo book : books) {
book.setStatusCN(book.getStatus() == 1 ? "可借阅" : "不可借阅");
}
return books;
}
}
从代码中可以清晰看到:
- Spring Boot 负责项目启动和自动配置;
- Spring MVC 负责 Web 层的请求处理;
- Spring 核心(IoC/DI)负责管理 Bean 及其依赖关系。
二、IoC(控制反转):Spring 核心思想
1. 什么是 IoC?
IoC(Inversion of Control,控制反转)是 Spring 的核心思想,本质是将对象的创建、管理权限从程序代码转移到 IoC 容器,程序不再直接通过 new 关键字创建对象,而是从容器中获取所需对象。
简单说:传统开发是 “程序自己造对象”,IoC 开发是 “容器送对象”。
2. 传统开发 vs IoC 开发:耦合度对比
以 “造车” 为例,直观感受两者的差异。
(1)传统开发:耦合度极高
传统开发中,对象的创建是 “自上而下” 的,上层对象依赖下层对象的创建,修改底层会导致整个调用链修改:
// 传统造车:Car 依赖 Framework,Framework 依赖 Bottom,Bottom 依赖 Tire
public class TraditionalCarDemo {
static class Car {
private Framework framework;
// Car 自己创建 Framework
public Car() {
this.framework = new Framework();
System.out.println("Car init...");
}
}
static class Framework {
private Bottom bottom;
// Framework 自己创建 Bottom
public Framework() {
this.bottom = new Bottom();
System.out.println("Framework init...");
}
}
static class Bottom {
private Tire tire;
// Bottom 自己创建 Tire
public Bottom() {
this.tire = new Tire();
System.out.println("Bottom init...");
}
}
static class Tire {
private int size = 17;
public Tire() {
System.out.println("Tire init: size=" + size);
}
}
public static void main(String[] args) {
// 只需要创建 Car,后续对象会被递归创建
new Car();
}
}
问题:如果需要修改轮胎尺寸(比如从 17 改为 20),需要修改 Tire、Bottom、Framework、Car 四个类的构造函数,耦合度极高,可维护性差。
(2)IoC 开发:彻底解耦
IoC 开发中,对象的创建是 “自下而上” 的,上层对象通过 “注入” 获取下层对象,不再自己创建:
// IoC 造车:对象通过构造函数注入,不再自己创建依赖
public class IocCarDemo {
static class Car {
private Framework framework;
// 注入 Framework,而非自己创建
public Car(Framework framework) {
this.framework = framework;
System.out.println("Car init...");
}
}
static class Framework {
private Bottom bottom;
// 注入 Bottom
public Framework(Bottom bottom) {
this.bottom = bottom;
System.out.println("Framework init...");
}
}
static class Bottom {
private Tire tire;
// 注入 Tire
public Bottom(Tire tire) {
this.tire = tire;
System.out.println("Bottom init...");
}
}
static class Tire {
private int size;
public Tire(int size) {
this.size = size;
System.out.println("Tire init: size=" + size);
}
}
public static void main(String[] args) {
// 先创建底层对象,再注入到上层
Tire tire = new Tire(20);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
}
}
优势:修改轮胎尺寸时,只需修改 Tire 的创建参数,其他类无需改动,彻底解耦。
3. IoC 的核心优势
- 解耦:消除对象间的直接依赖,修改一个类不会影响其他类;
- 可维护性:对象的创建、初始化统一由容器管理,便于统一配置和修改;
- 可扩展性:新增功能时,只需新增 Bean 并配置依赖,无需修改现有代码;
- 资源集中管理:容器统一管理对象的生命周期(创建、销毁),避免资源泄漏。
三、DI(依赖注入):IoC 的实现方式
1. 什么是 DI?
DI(Dependency Injection,依赖注入)是 IoC 思想的具体实现:容器在创建 Bean 时,自动将其依赖的其他 Bean 注入到当前 Bean 中,无需程序手动获取。
简单说:IoC 是 “让容器管对象” 的思想,DI 是 “容器给对象送依赖” 的具体操作。
2. IoC 与 DI 的关系
- IoC 是 “目标”:核心是反转对象的控制权;
- DI 是 “手段”:通过注入依赖的方式,实现控制权反转。
比如:“吃一顿好的” 是思想(IoC),“吃海底捞” 是具体实现(DI)。
3. DI 的实战体现
在 Spring 中,通过 @Autowired 等注解实现 DI,例如:
@Service
public class BookService {
// 容器自动注入 BookDao 对象,无需手动 new
@Autowired
private BookDao bookDao;
public List<BookInfo> getBookList() {
return bookDao.mockData(); // 直接使用注入的依赖
}
}
四、Spring 中 Bean 的存储与获取
Bean 是 Spring 容器管理的核心对象(如 Service、Dao、Controller 等)。要让 Spring 管理 Bean,需先 “存储” Bean,再根据需求 “获取” Bean。
1. Bean 的存储:两种注解方式
(1)类注解:快速存储 Bean
适用于自定义类,直接在类上添加注解,Spring 会自动扫描并创建 Bean。Spring 提供 5 种类注解,对应不同的应用分层:
| 注解 | 适用场景 | 作用 |
|---|---|---|
| @Controller | 控制层(Web 层) | 处理 HTTP 请求,返回响应,标识为 Web 层 Bean |
| @Service | 业务逻辑层 | 处理核心业务逻辑,标识为业务层 Bean |
| @Repository | 数据访问层(Dao 层) | 操作数据库(或数据存储),标识为数据层 Bean,还会自动处理数据库异常 |
| @Component | 通用组件层 | 无明确分层的通用 Bean(如工具类),是其他类注解的 “父注解” |
| @Configuration | 配置层 | 项目配置类(如数据源配置、Bean 配置),标识为配置类 Bean |
使用示例:
// 控制层 Bean
@Controller
public class UserController {
public void sayHi() {
System.out.println("Hi, UserController!");
}
}
// 业务层 Bean
@Service
public class UserService {
public void sayHi() {
System.out.println("Hi, UserService!");
}
}
// 数据层 Bean
@Repository
public class UserDao {
public void sayHi() {
System.out.println("Hi, UserDao!");
}
}
// 配置类 Bean
@Configuration
public class AppConfig {
public void config() {
System.out.println("App config...");
}
}
(2)方法注解 @Bean:灵活存储 Bean
适用于第三方类(如 User、DataSource)或需要多个实例的场景,通过方法返回 Bean 对象,需配合类注解使用(否则 Spring 无法扫描到)。
使用示例:
// 配合 @Component 类注解,让 @Bean 生效
@Component
public class BeanConfig {
// 存储 User 实例,Bean 名称默认是方法名 "user1"
@Bean
public User user1() {
User user = new User();
user.setName("张三");
user.setAge(18);
return user;
}
// 存储多个 User 实例,通过 name 属性自定义 Bean 名称
@Bean(name = {"user2", "lisi"})
public User user2() {
User user = new User();
user.setName("李四");
user.setAge(19);
return user;
}
}
注意:
- @Bean 必须配合类注解(如 @Component、@Configuration)使用,否则 Spring 无法识别;
- 若未指定 name 属性,Bean 名称默认是方法名;
- 支持多个名称(如
name = {"user2", "lisi"}),用逗号或数组分隔。
2. Bean 的获取:三种方式
获取 Bean 需先获取 Spring 上下文(ApplicationContext),再通过上下文获取 Bean。常用三种方式:
(1)通过类型获取(推荐,简洁)
@SpringBootApplication
public class SpringIocDemo {
public static void main(String[] args) {
// 1. 获取 Spring 上下文
ApplicationContext context = SpringApplication.run(SpringIocDemo.class, args);
// 2. 通过类型获取 Bean
UserController userController = context.getBean(UserController.class);
// 3. 使用 Bean
userController.sayHi(); // 输出:Hi, UserController!
}
}
(2)通过名称获取(需强转)
// 通过 Bean 名称获取(名称规则见下文)
UserController userController = (UserController) context.getBean("userController");
(3)通过类型 + 名称获取(解决同类型多个 Bean 冲突)
// 明确指定 Bean 名称和类型,无需强转
User user = context.getBean("user2", User.class);
System.out.println(user); // 输出:User(name=李四, age=19)
3. 上下文对比:ApplicationContext vs BeanFactory
Spring 容器有两个核心接口,面试常考:
| 特性 | ApplicationContext(推荐) | BeanFactory(轻量) |
|---|---|---|
| 继承关系 | 继承 BeanFactory,功能更全 | 顶层接口,提供基础功能 |
| 初始化方式 | 启动时一次性创建所有 Bean(空间换时间) | 按需创建 Bean(用时才创建,时间换空间) |
| 额外功能 | 支持国际化、资源访问、事件传播 | 仅提供 Bean 存储和获取功能 |
| 适用场景 | 大部分应用(Web 应用、Spring Boot) | 资源受限场景(如嵌入式设备) |
五、依赖注入的三种方式:优缺点与实战
Spring 提供三种依赖注入方式,适用于不同场景,需根据需求选择:
1. 属性注入(最常用,简洁)
通过 @Autowired 直接标注字段,容器自动注入依赖。
示例:
@Controller
public class UserController {
// 直接注入 UserService
@Autowired
private UserService userService;
public void sayHi() {
userService.sayHi(); // 输出:Hi, UserService!
}
}
优缺点:
- 优点:代码简洁,无需写构造函数或 Setter 方法;
- 缺点:
- 无法注入
final修饰的字段(final 字段需在构造时初始化); - 仅支持 IoC 容器,非 IoC 环境(如单元测试)无法使用,容易出现空指针异常;
- 不符合 Java 规范(字段私有化,应通过构造或 Setter 访问)。
- 无法注入
2. 构造方法注入(Spring 4.X 推荐)
通过构造函数注入依赖,@Autowired 可省略(若只有一个构造函数)。
示例:
@Controller
public class UserController {
private final UserService userService; // 支持 final 字段
// 构造方法注入,@Autowired 可省略
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHi() {
userService.sayHi();
}
}
优缺点:
- 优点:
- 支持
final字段,确保依赖不可修改; - 依赖在对象创建时初始化,避免空指针异常;
- 通用性强,符合 Java 规范,换框架也能使用;
- 支持
- 缺点:
- 依赖较多时,构造函数参数过长,代码繁琐。
3. Setter 注入(Spring 3.X 推荐)
通过 Setter 方法注入依赖,需在方法上添加 @Autowired。
示例:
@Controller
public class UserController {
private UserService userService;
// Setter 方法注入
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi() {
userService.sayHi();
}
}
优缺点:
- 优点:
- 灵活性高,可在对象创建后重新注入依赖;
- 符合 JavaBean 规范;
- 缺点:
- 无法注入
final字段; - 依赖可能被多次修改(Setter 方法可被重复调用);
- 对象创建时依赖可能未初始化,存在空指针风险。
- 无法注入
4. 三种注入方式对比表
| 注入方式 | 支持 final | 通用性 | 代码简洁度 | 依赖安全性 |
|---|---|---|---|---|
| 属性注入 | 不支持 | 低 | 高 | 低 |
| 构造方法注入 | 支持 | 高 | 中 | 高 |
| Setter 注入 | 不支持 | 中 | 中 | 中 |
推荐用法:
- 依赖较少(1-2 个):属性注入(简洁);
- 依赖较多(3 个以上):构造方法注入(安全);
- 需要动态修改依赖:Setter 注入(灵活)。
六、解决多个同类型 Bean 的冲突
当 Spring 容器中存在多个同类型 Bean 时,使用 @Autowired 会报错(无法确定注入哪个),需通过以下三种方式解决:
1. @Primary:指定默认 Bean
在多个同类型 Bean 中,用 @Primary 标注默认注入的 Bean。
示例:
@Component
public class BeanConfig {
// 标注为默认 Bean
@Primary
@Bean("user1")
public User user1() {
User user = new User();
user.setName("张三");
return user;
}
@Bean("user2")
public User user2() {
User user = new User();
user.setName("李四");
return user;
}
}
// 注入时,默认选择 @Primary 标注的 user1
@Controller
public class UserController {
@Autowired
private User user; // 注入的是 user1(张三)
}
2. @Qualifier:指定 Bean 名称
配合 @Autowired 使用,通过 value 属性指定要注入的 Bean 名称。
示例:
@Controller
public class UserController {
// 明确指定注入名称为 "user2" 的 Bean
@Autowired
@Qualifier("user2")
private User user; // 注入的是 user2(李四)
}
注意:@Qualifier 不能单独使用,必须配合 @Autowired。
3. @Resource:按名称注入(JDK 提供)
@Resource 是 JDK 自带的注解(非 Spring 提供),默认按名称注入,可通过 name 属性指定 Bean 名称。
示例:
@Controller
public class UserController {
// 按名称注入 "user2"
@Resource(name = "user2")
private User user; // 注入的是 user2(李四)
}
优势:
- 无需依赖 Spring 框架,兼容性更强;
- 支持更多属性(如
name、type),配置更灵活。
七、Bean 的命名规则
Spring 对 Bean 的命名有明确约定,避免手动配置时出现错误:
1. 类注解(@Controller、@Service 等)的 Bean 命名
- 默认规则:类名首字母小写,其余驼峰命名(如
UserController→userController); - 特殊情况:类名前两个字母均为大写时,保留原类名(如
UController→UController、AManager→AManager); - 自定义名称:通过注解的
value属性指定(如@Controller(value = "myUserController")或简写@Controller("myUserController"))。
2. 方法注解(@Bean)的 Bean 命名
- 默认规则:Bean 名称 = 方法名(如
@Bean public User user1()→ 名称为user1); - 自定义名称:通过
name属性指定(如@Bean(name = {"u1", "user1"}))。
八、Spring 扫描路径:确保 Bean 被正确识别
Spring 不会自动扫描所有包下的 Bean,需满足扫描路径规则才能识别注解:
1. 默认扫描路径
Spring Boot 中,@SpringBootApplication 注解默认包含 @ComponentScan,扫描路径为启动类所在包及其子包。
例如:启动类
BookApplication位于com.example.demo包下,Spring 会自动扫描com.example.demo及所有子包(如com.example.demo.controller、com.example.demo.service)。
2. 自定义扫描路径
若 Bean 不在默认扫描路径下,需通过 @ComponentScan 手动指定:
// 扫描多个包:com.example.demo 和 com.example.util
@ComponentScan({"com.example.demo", "com.example.util"})
@SpringBootApplication
public class BookApplication {
public static void main(String[] args) {
SpringApplication.run(BookApplication.class, args);
}
}
推荐实践:将启动类放在项目根包下(如 com.example.demo),所有业务代码放在子包中,无需手动配置扫描路径。
九、常见面试题总结(高频考点)
1. 请谈谈 IoC 与 DI 的关系
- IoC(控制反转)是核心思想:将对象的创建、管理权从程序转移到容器,解决对象耦合问题;
- DI(依赖注入)是 IoC 的实现方式:容器在创建 Bean 时,自动注入其依赖的其他 Bean;
- 关系:IoC 是 “目标”,DI 是 “手段”,两者描述的是同一件事(解耦),只是角度不同(IoC 从容器角度,DI 从应用程序角度)。
2. 三种依赖注入方式的优缺点
| 注入方式 | 支持 final | 通用性 | 代码简洁度 | 依赖安全性 |
|---|---|---|---|---|
| 属性注入 | 不支持 | 低 | 高 | 低 |
| 构造方法注入 | 支持 | 高 | 中 | 高 |
| Setter 注入 | 不支持 | 中 | 中 | 中 |
3. @Autowired 与 @Resource 的区别
| 对比维度 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring 框架提供 | JDK 自带(java.annotation) |
| 注入方式 | 默认按类型注入 | 默认按名称注入 |
| 支持属性 | 仅支持 required(是否必须注入) |
支持 name、type 等属性 |
| 兼容性 | 仅支持 Spring 容器 | 支持所有 IoC 容器(如 Spring、JBoss) |
| 配合注解 | 需配合 @Qualifier 指定名称 |
直接通过 name 指定名称 |
4. Spring、Spring MVC、Spring Boot 的关系
- Spring 是基础框架,核心是 IoC 容器,提供 AOP、事务管理等核心功能;
- Spring MVC 是 Spring 的子模块,专注于 Web 开发,处理 HTTP 请求、路由映射等;
- Spring Boot 是 Spring 的 “脚手架”,通过自动配置、Starter 依赖简化项目搭建,底层依赖 Spring 和 Spring MVC;
- 总结:Spring 是 “地基”,Spring MVC 是 “Web 楼层”,Spring Boot 是 “快速盖楼工具”。
5. BeanFactory 与 ApplicationContext 的区别
| 特性 | ApplicationContext(推荐) | BeanFactory(轻量) |
|---|---|---|
| 继承关系 | 继承 BeanFactory,功能更全 | 顶层接口,提供基础功能 |
| 初始化方式 | 启动时一次性创建所有 Bean(空间换时间) | 按需创建 Bean(用时才创建,时间换空间) |
| 额外功能 | 支持国际化、资源访问、事件传播 | 仅提供 Bean 存储和获取功能 |
| 适用场景 | 大部分应用(Web 应用、Spring Boot) | 资源受限场景(如嵌入式设备) |
十、总结
Spring 生态的核心是 IoC/DI,通过容器管理 Bean 及其依赖,实现代码解耦。Spring MVC 专注于 Web 开发,Spring Boot 简化配置,三者分工明确、相辅相成。
掌握本文核心知识点:
- 理清 Spring、Spring MVC、Spring Boot 的关系;
- 理解 IoC/DI 的核心思想与实操;
- 熟练使用 Bean 的存储(类注解、@Bean)与获取方式;
- 选择合适的依赖注入方式;
- 解决 Bean 冲突与扫描路径问题。
更多推荐



所有评论(0)