

Spring 框架是一个轻量级、一站式、模块化的开源框架,旨在简化企业级应用程序开发。它是一个包含众多工具方法的 IoC 容器,支持广泛的应用场景,并具有活跃而庞大的社区。
Spring 的主要功能包括:管理对象及其之间的依赖关系、面向切面编程 (AOP)、数据库事务管理、数据访问以及Web 框架支持等。
为了更好地理解 Spring 生态系统,我们首先要明确三者之间的关系:
框架名称 | 定位/核心功能 | 与其他框架关系 |
|---|---|---|
Spring | 核心框架,提供 IoC 和 AOP 等基础功能。 | 基础和核心,其他两个框架都建立在 Spring 之上。 |
Spring MVC | Spring 的子框架,用于构建 Web 应用和网络接口。 | 基于 Spring 开发的 Web 框架,提供 URL 映射和视图集成等功能。 |
Spring Boot | 一套快速开发整合包,是对 Spring 的封装和简化。 | 辅助简化项目开发,通过“约定大于配置”快速搭建 Spring 应用,例如通过引入 Spring MVC 框架来完成 Web 开发。 |
简单来说,Spring 是地基,Spring MVC 是 Web 层的工具,而 Spring Boot 是帮你快速搭建和整合项目的一把“脚手架”。
在技术领域,容器本质上是用来容纳某种物品的基本装置。在编程中,我们常见的容器包括:
List 或 Map。
IoC 是 Inversion of Control 的缩写,中文即控制反转。它是 Spring 的核心思想。
在传统的开发模式中,当一个对象需要依赖另一个对象时,开发者通常需要手动通过 new 关键字来创建这个依赖对象。而在 IoC 模式下,获得依赖对象的过程被反转了。开发者不再需要自己创建对象,而是将创建对象的任务交给 IoC 容器来完成。
💡 核心: 对象的创建和管理权从应用程序代码(使用方)反转到了 IoC 容器(第三方)。
DI 是 Dependency Injection 的缩写,中文即依赖注入。
**依赖注入(DI)**是指容器在程序运行期间,动态地为应用程序提供其运行时所依赖的资源(即对象)的过程。
从应用程序的角度来看,通过引入 IoC 容器,并利用依赖关系注入的方式,最终实现了对象之间的解耦。
通过一个“造车”的案例,我们可以直观地理解 IoC 解决的耦合度过高的问题。
在传统的程序设计中,依赖关系是自上而下的:汽车(Car)依赖车身(Framework),车身依赖底盘(Bottom),底盘依赖轮胎(Tire)。

在代码实现中,上层类会主动创建并控制下层依赖对象。例如,Car 类在其构造函数中会主动 new Framework(),Framework 会 new Bottom(),以此类推:
// 传统模式下,Car内部主动创建依赖对象Framework
static class Car {
private Framework framework;
public Car() {
framework = new Framework(); // ⚠️ 耦合点1:Car主动创建Framework
System.out.println("Car init....");
}
// ...
}
// Framework 内部主动创建依赖对象Bottom
static class Framework {
private Bottom bottom;
public Framework() {
bottom = new Bottom(); // ⚠️ 耦合点2:Framework主动创建Bottom
System.out.println("Framework init...");
}
// ...
}
// ...以此类推,当Tire类构造函数发生变更时...问题分析:
如果需求变动,例如需要为 Tire 类添加一个尺寸参数,那么 Tire 构造函数的变化会沿着整个调用链向上影响到 Bottom、Framework,最终导致 Car 类的代码也必须修改。这导致程序的耦合度非常高。
为了解决高耦合问题,我们采用依赖注入的方式,将原来由类自身创建下级类,改为通过**传递(注入)**的方式获取。
graph TD
D[轮胎(Tire)] -->|依赖| C[底盘(Bottom)]
C -->|依赖| B[车身(Framework)]
B -->|依赖| A[汽车(Car)]在 IoC 模式中,对象之间的依赖关系是反向的(控制权反转),对象的创建顺序也颠倒了:Tire
Bottom
Framework
Car。
以下是基于构造方法注入的 IoC 改造示例代码:
// IoC 改造后的 Car 类:不再主动创建 Framework,而是通过构造方法接收
static class Car {
private Framework framework;
// 依赖对象(Framework)由外部注入
public Car(Framework framework) {
this.framework = framework; // ✅ 注入点
System.out.println("Car init....");
}
// ...
}
// IoC 改造后的 Framework 类
static class Framework {
private Bottom bottom;
// 依赖对象(Bottom)由外部注入
public Framework(Bottom bottom) {
this.bottom = bottom; // ✅ 注入点
System.out.println("Framework init...");
}
// ...
}
// ...
// 对象的创建和组装工作集中在“IoC容器”中完成
public class IocCarExample {
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);
car.run();
}
}可以看到,Car、Framework 等类本身不再关心如何创建其依赖对象,它们只需要通过构造函数接收即可。即使 Tire 类构造函数变化,也只需要修改最顶层的调用程序(main 方法中的组装逻辑),底层的业务类代码不受影响。
控制反转带来的优势主要体现在两个方面:
Spring IoC 容器管理的对象被称为 Bean。要将一个对象交给 Spring 容器管理,我们需要使用特定的注解来声明它。
Spring 提供了两类注解用于 Bean 的存储:
@Controller、@Service、@Repository、@Component、@Configuration。@Bean。
Spring 提供了五种用于将类标记为 Bean 的注解,它们都属于 @Component 的衍生注解。使用不同的注解是为了实现应用分层,让开发者看到注解就能直观了解类的用途。
注解名称 | 对应应用层 | 职责描述 | 示例类名 |
|---|---|---|---|
@Controller | 控制层 (Web/API) | 接收请求、处理请求并进行响应。 | UserController |
@Service | 业务逻辑层 | 处理具体的业务逻辑,编排和调用 Dao/Repository 层。 | UserService |
@Repository | 数据访问层 (持久层) | 负责数据访问操作,与数据库交互。 | UserRepository |
@Configuration | 配置层 | 处理项目中的配置信息,通常与 @Bean 配合使用。 | AppConfig |
@Component | 通用组件 | 泛指任何不属于上述层级的通用组件或工具类。 | ToolComponent |
@Controller:控制层用于标注处理 HTTP 请求的控制器类。
@Controller // 将对象存储到 Spring 中
public class UserController {
public void sayHi() {
System.out.println("hi, UserController...");
}
}@Service:业务逻辑层用于标注业务逻辑处理类。
@Service
public class UserService {
public void sayHi (String name) {
System.out.println("Hi," + name);
}
}@Repository:数据访问层/持久层用于标注数据访问或持久化操作的类。
@Repository
public class UserRepository {
public void sayHi() {
System.out.println("Hi, UserRepository~");
}
}@Configuration:配置层用于标注配置类,通常包含 @Bean 方法用于创建 Bean。
@Configuration
public class UserConfiguration {
public void sayHi() {
System.out.println("Hi, UserConfiguration~");
}
}@Component:通用组件一个通用注解,是所有组件存储注解的“父类”。
@Component
public class UserComponent {
public void sayHi() {
System.out.println("Hi, UserComponent~");
}
}@Component 的衍生查看 @Controller、@Service、@Repository 等注解的源码,我们会发现它们内部都包含了 @Component 注解。
因此,@Component 被称为元注解,而 @Controller、@Service、@Repository 等是它的衍生注解。它们在功能上等价于 @Component,但提供了更明确的语义,便于应用分层和工具处理。在实际开发中,我们应尽量使用语义更明确的衍生注解,如在业务逻辑层使用 @Service,而非 @Component。
@Bean 的灵活应用@Bean 注解是添加到方法上的,用于将方法的返回值对象注册为 Spring Bean。它主要用于解决以下问题:
注意:
@Bean方法必须定义在一个被@Component或@Configuration(本身也包含@Component)注解标记的类中才能生效。
// BeanConfig 必须被组件注解标记
@Component
public class BeanConfig {
@Bean // 将方法的返回值对象注册为 Bean
public User user() {
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}Bean 的默认命名规则
在使用 @Bean 标记方法时,如果没有显式指定名称,Spring 会默认将方法名作为 Bean 的名称(BeanId)。
// Bean 名称默认为 "user1"
@Bean
public User user1() { /* ... */ }
// Bean 名称默认为 "user2"
@Bean
public User user2() { /* ... */ }Bean 名称可以通过设置 @Bean 注解的 name 属性或直接作为参数,为 Bean 对象进行重命名,并且可以指定多个别名。
// 使用 name 属性指定多个别名
@Bean(name = {"u1", "user1Alias"})
public User user1() { /* ... */ }
// name={} 可以省略,直接传入字符串数组
@Bean({"u2", "user2Alias"}) // Bean 名称/别名为 "u2" 和 "user2Alias"
public User user2() { /* ... */ }
// 只有一个名称时,{} 也可以省略
@Bean("u3") // Bean 名称为 "u3"
public User user3() { /* ... */ }@SpringBootApplication使用五大类注解声明的 Bean,想要生效,必须被 Spring 扫描到。
Spring 通过 @ComponentScan 注解来配置扫描路径。
@ComponentScan({"com.example.demo.controller", "com.example.demo.service"})
@SpringBootApplication
// ...在 Spring Boot 应用中,通常无需手动添加 @ComponentScan,这是因为启动类上的 @SpringBootApplication 注解已经默认包含了 @ComponentScan。
默认扫描范围:Spring Boot 启动类 (@SpringBootApplication 所在的类) 所在包及其所有子包。
推荐做法:将 Spring Boot 启动类 (SpringIocDemoApplication) 放置在项目根包下(例如 com.example.demo),这样它就能默认扫描到所有的子包(如 controller、service、repository 等)中定义的 Bean。

依赖注入是 IoC 容器在创建 Bean 时,为其提供运行时所依赖的资源(对象)的过程。Spring 提供了三种主要的依赖注入方式:
所有注入方式主要通过 @Autowired 注解实现。
在类的成员属性上使用 @Autowired 注解。这种方式最简洁、方便。
@Controller
public class UserController {
// 注入方法1: 属性注入
@Autowired
private UserService userService; //
public void sayHi () {
System.out.println("hi, UserController...");
userService.sayHi(); // 如果没有 @Autowired,这里会抛出 NullPointerException
}
}在类的构造方法上使用 @Autowired 注解。如果类中只有一个构造方法,@Autowired 可以省略。
@Controller
public class UserController2 {
private final UserService userService; // 推荐使用 final 修饰
@Autowired // 如果是唯一的构造方法,@Autowired 可省略
public UserController2(UserService userService) {
this.userService = userService;
}
// ...
}在类的 Setter 方法上使用 @Autowired 注解。
@Controller
public class UserController3 {
private UserService userService;
@Autowired // 在 Setter 方法上添加 @Autowired
public void setUserService (UserService userService) {
this.userService = userService;
}
// ...
}注入方式 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
属性注入 | 简洁、使用方便。 | 1. 只能用于 IoC 容器,耦合度较高。 2. 无法注入 final 属性。 3. 只有在使用时才会抛出空指针异常 (NPE)。 | 不推荐 |
构造方法注入 | 1. 可以注入 final 修饰的属性。 2. 依赖对象在使用前一定被完全初始化。 3. 注入的对象不会被修改(天然不可变)。 4. 通用性好,JDK支持。 | 注入多个对象时,代码会比较繁琐。 | Spring 4.X/5.X 推荐 |
Setter 注入 | 方便在类实例之后,重新对该对象进行配置或注入。 | 1. 不能注入 final 属性。 2. 注入对象可能会被修改(setter 可被多次调用)。 | Spring 3.X 推荐 |
最佳实践: 构造方法注入是 Spring 官方推荐的依赖注入方式,因为它保证了依赖的不可变性(
final)和非空性(依赖必须在构造时传入)。
@Autowired、@Qualifier 与 @Resource当 Spring 容器中存在多个相同类型的 Bean 时,如果仅使用 @Autowired 按类型注入,程序会因无法确定注入哪个 Bean 而报错 NoUniqueBeanDefinitionException(非唯一 Bean 对象)。
@Autowired 装配顺序为了解决多 Bean 冲突,Spring 提供了明确的装配顺序:
@Qualifier 或 @Primary: @Qualifier,则按 Qualifier 参数查找 Bean。@Qualifier,则进入下一步。@Primary 指定默认 Bean当存在多个相同类型的 Bean 时,在其中一个 Bean 的定义上加上 @Primary 注解,可以指定它为默认实现。
@Component
public class BeanConfig {
@Primary // 指定该 bean 为默认 bean 的实现
@Bean("u1")
public User user1() { /* user1: zhangsan */ }
@Bean
public User user2() { /* user2: lisi */ }
}
@Controller
public class UserController {
// 此时 @Autowired 会优先注入带有 @Primary 的 user1
@Autowired
private User user;
}@Qualifier 按名称匹配@Qualifier 注解用于指定当前要注入的 Bean 的名称,它必须配合 @Autowired 一起使用。
@Controller
public class UserController {
@Qualifier("user2") // 明确指定注入名为 "user2" 的 Bean
@Autowired
private User user; // 将注入 user2
}@Resource 按名称注入 【JDK标准】@Resource 是 JDK 提供的注解,默认是按照 Bean 的名称(即 name 属性)进行注入。
@Controller
public class UserController {
// 通过 name 属性指定注入名为 "user2" 的 Bean
@Resource(name = "user2")
private User user; // 将注入 user2
}@Autowired 与 @Resource 的区别特性 | @Autowired | @Resource |
|---|---|---|
提供方 | Spring 框架 | JDK (Java 标准) |
默认注入方式 | **按类型(Type)**注入 | **按名称(Name)**注入 |
名称查找 | 在按类型查找失败(多 Bean 冲突)后,会尝试按名称查找。 | 直接通过 name 属性或字段名查找 Bean。 |
配合注解 | 可配合 @Qualifier 实现按名称注入。 | 支持更多参数设置,例如 name 属性。 |
推荐使用: 在 Spring Boot/Spring Cloud 项目中,@Autowired 配合构造方法注入是首选;如果需要兼容 Java EE 或按名称明确指定依赖,可以使用 @Resource。
本文对 Spring IoC 和 DI 进行了全面且深入的探讨:
@Controller、@Service、@Repository、@Configuration (均是 @Component 的衍生) 和 @Bean 来将对象注册到 Spring 容器中。@Primary、@Qualifier(配合 @Autowired)或 @Resource 来解决多类型 Bean 注入冲突问题。掌握 IoC 和 DI,是迈向 Spring 高级开发的第一步。它们是 Spring 框架的基石,也是设计高内聚、低耦合、可维护系统的关键。