文章目录
- [一、Spring、Spring MVC、Spring Boot三者关系](#一、Spring、Spring MVC、Spring Boot三者关系)
- 二、IoC控制反转
-
- [2.1 定义](#2.1 定义)
- [2.2 传统开发高耦合](#2.2 传统开发高耦合)
- [2.3 IoC解耦](#2.3 IoC解耦)
- [2.4 IoC容器两大核心能力](#2.4 IoC容器两大核心能力)
- 三、DI依赖注入
- 四、Bean如何存入Spring容器
-
- [4.1 @Component 与 @Autowired](#4.1 @Component 与 @Autowired)
- [4.2 五大类注解](#4.2 五大类注解)
-
- [1. 注解层级关系](#1. 注解层级关系)
- [2. Bean命名规则](#2. Bean命名规则)
- [3. 容器获取Bean三种方式](#3. 容器获取Bean三种方式)
- [4.3 @Bean方法注解](#4.3 @Bean方法注解)
-
- [1. 适用场景](#1. 适用场景)
-
- 场景1:第三方外部类,无法加类注解
- [场景2:同一个类需要多个不同配置的 Bean](#场景2:同一个类需要多个不同配置的 Bean)
- [2. 使用规范](#2. 使用规范)
- [3. 命名规则](#3. 命名规则)
- [4.4 Bean扫描范围与@ComponentScan](#4.4 Bean扫描范围与@ComponentScan)
-
- 默认扫描规则
- @ComponentScan
-
- [与 @SpringBootApplication 的关系](#与 @SpringBootApplication 的关系)
- 手动自定义扫描路径
- [BeanFactory 与 ApplicationContext](#BeanFactory 与 ApplicationContext)
- 五、依赖注入方式
-
- [5.1 属性注入](#5.1 属性注入)
- [5.2 构造方法注入](#5.2 构造方法注入)
- [5.3 Setter方法注入](#5.3 Setter方法注入)
- 六、多Bean注入冲突与解决方案
一、Spring、Spring MVC、Spring Boot三者关系
- Spring :底层框架,本质是IoC容器,提供IoC、AOP、事务、资源管理等基础能力;
- Spring MVC :Spring的子模块,专门做Web接口开发,提供
@RestController、@RequestMapping等Web注解; - Spring Boot:Spring的脚手架,简化Spring繁琐XML配置,自动装配、内置服务器,快速搭建项目。
分层协作流程:
前端请求 → Spring MVC Controller → Spring Service业务层 → Spring Repository数据层 → 数据库
其中对象管理、依赖装配全部由Spring IoC容器实现。
二、IoC控制反转
2.1 定义
IoC全称Inversion of Control,控制反转是一种设计思想:
- 传统开发控制权 :开发者手动
new创建依赖对象,上层主动创建下层,代码高耦合; - IoC反转后控制权 :对象创建、销毁、依赖组装交给IoC容器(Spring),开发者只需要声明需要什么对象,容器自动提供。
2.2 传统开发高耦合
需求:造一辆车,依赖关系Car → Framework车身 → Bottom底盘 → Tire轮胎
在每个类内部new下级依赖
java
static class Car {
private Framework framework;
public Car() {
// 内部自己创建依赖,强耦合
framework = new Framework();
}
}
static class Framework {
private Bottom bottom;
public Framework() {
bottom = new Bottom();
}
}
static class Bottom {
private Tire tire;
public Bottom() {
tire = new Tire(17);
}
}
缺陷 :底层类修改,例如轮胎增加尺寸参数,整条调用链Tire→Bottom→Framework→Car全部要修改,牵一发而动全身,耦合度极高。
2.3 IoC解耦
不再在类内部new依赖,而是外部传入依赖:
java
public class Main {
public static void main(String[] args) {
Tire tire = new Tire(21);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
}
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");
}
}
public class Framework {
Bottom bottom ;
public Framework(Bottom bottom) {
this.bottom = bottom;
System.out.println("framework init...");
}
}
public class Bottom {
Tire tire;
public Bottom( Tire tire) {
this.tire = tire;
System.out.println("bottom init...");
}
}
public class Tire {
int size;
public Tire(int size){
this.size = size;
System.out.println("tire init,size: "+size);
}
}
变化:
- 对象创建顺序反转:传统
Car先创建→ IoC模式Tire先创建; - 解耦:底层Tire修改,上层Car、Framework代码完全不用改动;
- 统一管理:所有对象由第三方容器统一创建、维护。
2.4 IoC容器两大核心能力
Spring就是标准IoC容器,只做两件事:
- 存Bean:把类交给Spring管理,Spring创建对象存入容器;
- 取Bean:需要使用对象时,从容器获取,自动装配依赖。
Bean:Spring容器中管理的所有对象,统一称为Bean。
在上面的例子中,Car,Framework,Bottom,Tire对象管理就可以交给Spring来完成
三、DI依赖注入
DI全称Dependency Injection,依赖注入是实现IoC的具体技术手段 。
IoC容器运行时,自动将目标类需要的依赖对象,动态注入到类中。

IoC与DI关系区分:
- IoC:思想、目标,控制权交给容器,解耦;
- DI:实现方案,通过注入方式完成对象装配。
四、Bean如何存入Spring容器
4.1 @Component 与 @Autowired
@Component : 把类交给 Spring 管理
@Component 是一个类级别的注解,告诉 Spring:"这个类由你来管理,创建它的实例(Bean)并放进 IoC 容器中。"
例如: BookService 和 BookDao 加上 @Component:
java
@Component
public class BookService {
java
@Component
@Data
public class BookDao {
加上 @Component 后,Spring 在启动时就会自动创建 BookService 和 BookDao 的单例对象 放在 IoC 容器中,不需要手动 new。
注意 :@RestController 本身也包含了 @Component 的功能,所以加了 @RestController 的类也自动被 Spring 管理。
@Autowired :依赖注入
@Autowired 是一个字段/构造方法/Setter 级别的注解,告诉 Spring:"我需要一个该类型的对象,你从 IoC 容器中帮我注入进来。"
例如:在需要实例的代码中@Autowired ,就不需要手动创建实例,直接调用方法
java
@RestController
@RequestMapping("/book")
public class BookController {
@Autowired
private BookService bookService ;
@RequestMapping("/getList")
public List<BookInfo> getList() {
return bookService.getList();
}
}
java
@Component
public class BookService {
@Autowired
BookDao bookDao ;
public List<BookInfo> getList() {
bookDao.mockData();
return bookDao.getBookList();
}
}
BookController需要调用BookService的方法,声明了private BookService bookService字段- 加上
@Autowired后,Spring 在创建BookController实例时,会去 IoC 容器中找到BookService类型的 Bean 并自动赋值给这个字段 - 同理,Spring 创建
BookService时,也会自动把BookDao注入进去
这样就不需要写:
java
private BookService bookService = new BookService(); // ❌ 不需要手动创建
整体依赖链
BookController (@RestController)
│ @Autowired
▼
BookService (@Component)
│ @Autowired
▼
BookDao (@Component)
它们之间的依赖关系完全由 Spring 通过 @Autowired 自动装配,开发者只需要声明"我需要什么",不需要关心"怎么创建"。
| 注解 | 角色 | 解决的问题 |
|---|---|---|
@Component |
注册 | 告知 Spring 帮我创建和管理这个类的实例 |
@Autowired |
注入 | 告知 Spring 把我需要的依赖从容器中自动拿给我 |
两者配合,就实现了控制反转(IoC):对象的创建和组装不再由自己控制,而是交给 Spring 容器统一管理。
错误日志
- 假设BookDao没有
@Component注解,直接使用@Autowired实例化这个类:
java
Field bookDao in com.daisy.springbookdemo.service.BookService required a bean of type 'com.daisy.springbookdemo.dao.BookDao' that could not be found.
表示需要一个com.daisy.springbookdemo.dao.BookDao这样类型的对象,但是在IoC容器中找不到,因为缺少了@Component
- 假设BookDao使用了
@Component,BookService中没有使用@Autowired注解,直接实例化这个类
java
java.lang.NullPointerException: Cannot invoke "com.daisy.springbookdemo.dao.BookDao.mockData()" because "this.bookDao" is null
抛出空指针异常,因为@Autowired在Spring中没有取出BookDao,也就无法进行赋值,因此这个字段就是null
4.2 五大类注解
1. 注解层级关系
@Controller、@Service、@Repository、@Configuration底层都标注了@Component,是@Component衍生注解:
| 注解 | 使用分层 | 作用 |
|---|---|---|
| @Controller | 控制层 | 接收前端请求,Web接口 |
| @Service | 业务逻辑层 | 处理业务逻辑 |
| @Repository | 数据层 | 操作数据库DAO |
| @Configuration | 配置层 | 项目配置类 |
| @Component | 通用组件 | 不属于以上分层的工具类 |
这五个注解的共同作用都是把类交给Spring管理。
区别:
@Controller是控制层的注解,作用是接受参数,返回响应,因此控制层只能使用@Controller注解,其余的注解不能发挥作用- 剩余四个注解大部分情况功能都相同,但是不同注解的含义不同,最好按标准使用
2. Bean命名规则
- 默认规则:类名首字母小写,如
BookService→ bean名称bookService; - 特殊规则:类名前两个字母大写,名称不变,如
UController→UController; - 自定义名称:在注解中加上字符串,
@Service("book")手动指定Bean名称。
3. 容器获取Bean三种方式
通过ApplicationContext(Spring上下文,容器入口)获取:
java
// 1. 根据类型获取
BookService service = context.getBean(BookService.class);
// 2. 根据Bean名称获取
BookService service = (BookService) context.getBean("bookService");
// 3. 名称+类型双重匹配
BookService service = context.getBean("bookService", BookService.class);
4.3 @Bean方法注解
@Bean 是方法注解 ,写在配置类的方法上,将当前方法返回的对象注册为 Spring IoC 容器中的 Bean,交给 Spring 统一管理。
1. 适用场景
场景1:第三方外部类,无法加类注解
比如 DataSource、Redis客户端、Mybatis工具类,源码不在自己项目里,不能给类加 @Service/@Component,只能用 @Bean 创建对象交给容器。
java
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://xxx");
return ds;
}
}
场景2:同一个类需要多个不同配置的 Bean
比如多数据源、多个 Redis 实例,一个类要创建多个对象,类注解只能生成一个Bean,@Bean 可以定义多个方法生成多个实例:
java
@Component
public class StudentComponent {
@Bean
public Student s1 () {
return new Student("zhangsan", 18);
}
@Bean
public Student s2 () {
return new Student("lisi", 18);
}
}
2. 使用规范
@Bean 不能单独生效 ,必须配合@Component或者其他类注解配置类使用
,否则Spring扫描不到这个方法,无法注册Bean。
推荐使用 @Configuration,专门用于配置类,Spring会做CGLIB代理优化。
3. 命名规则
- 默认名称:不加name属性时,Bean名称 = 方法名
java
// bean名称:user
@Bean
public User user(){...}
- 自定义名称 :通过
name属性指定,支持多个别名
java
// bean别名:u1、userOne
@Bean({"u1","userOne"})//使用数组接收
public User user(){...}
// 简写
@Bean("u1")
public User user(){...}
4.4 Bean扫描范围与@ComponentScan
默认扫描规则
默认扫描启动类所在包 + 所有子包。
举例子:
启动类路径:com.example.demo.SpringIocDemoApplication
自动扫描范围:com.example.demo 下全部子包(controller/service/repository/component...)
如果Bean类放在启动类同级包/上级包 ,超出默认扫描范围,Spring找不到,直接报 No qualifying bean。
@ComponentScan
@ComponentScan 是Spring的包扫描注解 ,自动扫描指定包下所有标注了 @Component、@Controller、@Service、@Repository、@Configuration 的类,将这些类注册为Bean存入IoC容器。告诉Spring去哪里找带类注解的Bean。
与 @SpringBootApplication 的关系
@SpringBootApplication 是复合注解,源码内部已经包含 @ComponentScan:
java
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan // 内置了包扫描
public @interface SpringBootApplication {}
这里@ComponentScan的值就是启动类所在的目录
手动自定义扫描路径
当Bean不在启动类包下,手动指定扫描包:
java
//手动指定包
@ComponentScan("com.example.demo")
// 扫描多个包
@ComponentScan({"com.example.demo", "com.example.other"})
@SpringBootApplication
public class DemoApplication {}
注意:
- 数组可以填多个包路径,逗号分隔;
- 这只是一种解决扫描路径的方案,但是很繁琐,最优的方案是 把启动类放在项目根包,依靠默认扫描。
BeanFactory 与 ApplicationContext
BeanFactory :Spring 底层顶层接口,仅提供基础 Bean 获取能力,懒加载 (使用时才创建 Bean);
ApplicationContext :BeanFactory 子接口,企业级标准上下文,容器启动时一次性加载所有单例 Bean,额外支持国际化、事件发布、资源读取。
五、依赖注入方式
5.1 属性注入
Field注入,@Autowired写在字段上
java
@RestController
public class BookController {
// 属性注入
@Autowired
private BookService bookService;
}
- 优点:代码极简,开发快;
- 缺点:
- 仅能在IoC容器中使用,脱离容器直接new会空指针;
- 无法注入
final常量; - 依赖隐藏,无法直观看到类依赖哪些对象。
5.2 构造方法注入
- 如果只有一个构造方法,那么Spring也只能使用这个构造方法创建对象,
@Autowired可以省略 - 如果存在多个构造方法,默认构造方法是无参版本,可以使用
@Autowired指定默认的构造方法 - Spring官方推荐
java
@RestController
public class BookController {
private final BookService bookService;
// 类只有一个构造方法,@Autowired可省略
public BookController(BookService bookService) {
this.bookService = bookService;
}
}
- 优点:
- 支持
final修饰,依赖不可变; - 实例化时依赖必须全部注入,避免空指针;
- JDK支持,不依赖Spring容器,单元测试可直接new传入Mock对象;
- 支持
- 缺点:依赖较多时,构造方法参数冗长。
5.3 Setter方法注入
@Autowired写在set方法
java
@RestController
public class BookController {
private BookService bookService;
@Autowired
public void setBookService(BookService bookService) {
this.bookService = bookService;
}
}
- 优点:对象创建完成后,重新对这个对象进行配置或注入,可以动态调整;
- 缺点:不能注入final,set方法可以被多次调用,依赖可被多次修改,存在数据风险。
六、多Bean注入冲突与解决方案
多Bean注入冲突
当同一个类创建了多个Bean,@Autowired按类型查找会报错。
java
@Component
public class StudentComponent {
//创建两个Bean
@Bean
public Student s1 () {
return new Student("zhangsan", 18);
}
@Bean
public Student s2 () {
return new Student("lisi", 19);
}
}
public class UserService {
//Autowired按照Student类型查找
@Autowired
private Student stu;
public void print() {
System.out.println("do Service!");
System.out.println(stu);
}
}
报错信息:
java
Field student in com.daisy.springiocdemo.service.UserService required a single bean, but 2 were found:
- s1: defined by method 's1' in class path resource [com/daisy/springiocdemo/component/StudentComponent.class]
- s2: defined by method 's2' in class path resource [com/daisy/springiocdemo/component/StudentComponent.class]
This may be due to missing parameter name information
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
UserService中的student属性需要一个Bean,但是找到了两个对象,不知道该使用哪个对象,就会报错
Action中提供了两种解决方案。
解决方案
1. @Primary:设置默认Bean
在其中一个Bean上添加注解,Spring默认优先注入该对象:
java
@Configuration
public class StudentComponent {
@Primary
@Bean public Student s1 () {
return new Student("zhangsan", 18);
}
@Bean
public Student s2 () {
return new Student("lisi", 19);
}
}
2. @Qualifier + @Autowired:指定Bean名称
java
@Service
public class UserService {
//指定Bean名称为s1的对象
@Qualifier("s1")
@Autowired
private Student student;
public void print() {
System.out.println("do Service!");
System.out.println(student);
}
}
@Qualifier 也可以标注在 @Bean 方法参数上,精确指定注入哪个 Bean
举个例子:
java
@Component
public class StudentComponent {
@Bean
public String name1() {
return "zhangsan";
}
@Bean
public String name2() {
return "lisi";
}
@Bean
public Student s1 (@Qualifier("name1") String n) {
return new Student(n, 18);
}
@Bean
public Student s2 () {
return new Student("lisi", 19);
}
}
其中
java
@Bean
public Student s1 (@Qualifier("name1") String n) {
return new Student(n, 18);
}
s1() 方法需要注入一个 String 类型的参数 n。但容器里有两个 String 类型的 bean(name1 → "zhangsan"、name2 → "lisi")。
- **不加
@Qualifier**:
java
NoUniqueBeanDefinitionException: 找到 2 个 String 类型的 Bean,不知道用哪个
- 加了
@Qualifier("name1")之后: Spring 明确知道你要的是名字叫"name1"的那个 String bean(即"zhangsan"),所以s1创建出来的 Student 是Student("zhangsan", 18),而不是Student("lisi", 18)。
3. @Resource:按名称注入
@Resource是Jakarta提供的注解,可以直接通过name属性指定Bean名称:
java
@Service
public class UserService {
@Resource(name = "s1")
private Student student;
public void print() {
System.out.println("do Service!");
System.out.println(student);
}
}
对比第一中方式,后两种方式更灵活,不同场景下可以拿到不同的对象
@Autowired 和 @Resource 区别
| 对比项 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring框架专属注解 | JSR-250 Java官方标准注解 |
| 默认匹配规则 | 优先按类型 | 优先按名称,找不到再匹配类型 |
| 指定Bean名称 | 必须配合@Qualifier | 自带name属性直接指定 |
| required属性 | 支持,可设required=false允许为空 | 无该属性,依赖必须存在 |
- @Autowired:在多个Bean匹配时,如果字段名和Bean名称相同,就会匹配,不报错
- @Resource:按照优先,但一定是在类型相同的前提下
