【Java EE】Spring IoC 与 DI

文章目录

  • [一、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)
      • [@Component : 把类交给 Spring 管理](#@Component : 把类交给 Spring 管理)
      • [@Autowired :依赖注入](#@Autowired :依赖注入)
      • 整体依赖链
      • 错误日志
    • [4.2 五大类注解](#4.2 五大类注解)
      • [1. 注解层级关系](#1. 注解层级关系)
      • [2. Bean命名规则](#2. Bean命名规则)
      • [3. 容器获取Bean三种方式](#3. 容器获取Bean三种方式)
    • [4.3 @Bean方法注解](#4.3 @Bean方法注解)
      • [1. 适用场景](#1. 适用场景)
      • [2. 使用规范](#2. 使用规范)
      • [3. 命名规则](#3. 命名规则)
    • [4.4 Bean扫描范围与@ComponentScan](#4.4 Bean扫描范围与@ComponentScan)
  • 五、依赖注入方式
    • [5.1 属性注入](#5.1 属性注入)
    • [5.2 构造方法注入](#5.2 构造方法注入)
    • [5.3 Setter方法注入](#5.3 Setter方法注入)
  • 六、多Bean注入冲突与解决方案
    • 多Bean注入冲突
    • 解决方案
      • [1. @Primary:设置默认Bean](#1. @Primary:设置默认Bean)
      • [2. @Qualifier + @Autowired:指定Bean名称](#2. @Qualifier + @Autowired:指定Bean名称)
      • [3. @Resource:按名称注入](#3. @Resource:按名称注入)
    • [@Autowired 和 @Resource 区别](#@Autowired 和 @Resource 区别)

一、Spring、Spring MVC、Spring Boot三者关系

  1. Spring :底层框架,本质是IoC容器,提供IoC、AOP、事务、资源管理等基础能力;
  2. Spring MVC :Spring的子模块,专门做Web接口开发,提供@RestController@RequestMapping等Web注解;
  3. 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);  
    }  
}

变化

  1. 对象创建顺序反转:传统Car先创建 → IoC模式Tire先创建
  2. 解耦:底层Tire修改,上层Car、Framework代码完全不用改动;
  3. 统一管理:所有对象由第三方容器统一创建、维护。

2.4 IoC容器两大核心能力

Spring就是标准IoC容器,只做两件事:

  1. 存Bean:把类交给Spring管理,Spring创建对象存入容器;
  2. 取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 容器中。"

例如: BookServiceBookDao 加上 @Component

java 复制代码
@Component
public class BookService {
java 复制代码
@Component
@Data
public class BookDao {

加上 @Component 后,Spring 在启动时就会自动创建 BookServiceBookDao单例对象 放在 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();  
    }  
}
  1. BookController 需要调用 BookService 的方法,声明了 private BookService bookService 字段
  2. 加上 @Autowired 后,Spring 在创建 BookController 实例时,会去 IoC 容器中找到 BookService 类型的 Bean 并自动赋值给这个字段
  3. 同理,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 容器统一管理。

错误日志

  1. 假设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

  1. 假设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命名规则

  1. 默认规则:类名首字母小写,如 BookService → bean名称bookService
  2. 特殊规则:类名前两个字母大写,名称不变,如UControllerUController
  3. 自定义名称:在注解中加上字符串,@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. 命名规则

  1. 默认名称:不加name属性时,Bean名称 = 方法名
java 复制代码
// bean名称:user
@Bean
public User user(){...}
  1. 自定义名称 :通过 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 {}

注意:

  1. 数组可以填多个包路径,逗号分隔;
  2. 这只是一种解决扫描路径的方案,但是很繁琐,最优的方案是 把启动类放在项目根包,依靠默认扫描。
BeanFactory 与 ApplicationContext

BeanFactory :Spring 底层顶层接口,仅提供基础 Bean 获取能力,懒加载 (使用时才创建 Bean);

ApplicationContext :BeanFactory 子接口,企业级标准上下文,容器启动时一次性加载所有单例 Bean,额外支持国际化、事件发布、资源读取。

五、依赖注入方式

5.1 属性注入

Field注入,@Autowired写在字段上

java 复制代码
@RestController
public class BookController {
    // 属性注入
    @Autowired
    private BookService bookService;
}
  • 优点:代码极简,开发快;
  • 缺点:
    1. 仅能在IoC容器中使用,脱离容器直接new会空指针;
    2. 无法注入final常量;
    3. 依赖隐藏,无法直观看到类依赖哪些对象。

5.2 构造方法注入

  • 如果只有一个构造方法,那么Spring也只能使用这个构造方法创建对象,@Autowired可以省略
  • 如果存在多个构造方法,默认构造方法是无参版本,可以使用@Autowired指定默认的构造方法
  • Spring官方推荐
java 复制代码
@RestController
public class BookController {
    private final BookService bookService;
    // 类只有一个构造方法,@Autowired可省略
    public BookController(BookService bookService) {
        this.bookService = bookService;
    }
}
  • 优点:
    1. 支持final修饰,依赖不可变;
    2. 实例化时依赖必须全部注入,避免空指针;
    3. 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:按照优先,但一定是在类型相同的前提下
相关推荐
SimonKing18 小时前
阿里要求全员卸载 Claude Code:事件始末与深层逻辑
java·后端·程序员
云烟成雨TD18 小时前
Agent Scope Java 2.x 系列【42】2.0 GA 正式发布:从透明开发迈向智能体系统工程
java·人工智能·agent
猫猫不是喵喵.19 小时前
SpringBoot自动装配原理
java·spring boot·后端
刘小八19 小时前
Redis 缓存一致性:一文讲透延时双删原理、并发时序
java·数据库·redis·缓存
ruleslol19 小时前
Spring11-校验框架:@Valid VS @Validated
spring
吃饱了得干活19 小时前
从0到1实现消息已读未读:从基础设计到高并发架构
java·后端·架构
r_oo_ki_e_19 小时前
java微服务
java·微服务
weixin_BYSJ198719 小时前
django在线图书销售平台---附源码16192
java·javascript·spring boot·python·django·flask·php
糖果店的幽灵19 小时前
【langgraph 从入门到精通graphApi 篇】Command 与动态流程控制
android·java·数据库·人工智能·langgraph