跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
Javajava

Spring IoC 与依赖注入详解

综述由AI生成Spring 是一个开源 Java 开发框架,核心目标是简化企业级应用开发。其核心思想是控制反转(IoC),将对象的创建和管理权从“谁使用谁控制”转变为由框架统一管理,降低组件耦合度。依赖注入(DI)是 IoC 的实现方式,通过容器在运行时自动将依赖对象注入到目标组件中。文章详细介绍了 Spring Bean 的存储方式(类注解与方法注解)、Bean Name 命名规则、扫描路径配置,以及@Autowired 和@Resource 的区别。此外,还对比了属性注入、构造方法注入和 Setter 注入三种方式的优缺点,帮助开发者选择最佳实践。

战神发布于 2026/3/15更新于 2026/4/2611 浏览

1. Spring 是什么?

Spring 是一个开源的 Java 开发框架,核心目标是简化企业级应用开发。它提供了模块化工具(如 Spring MVC、Spring Security、Spring Data 等),让开发者更专注于业务逻辑,而非重复的底层代码。

容器是什么?

在 Spring 中,容器是一个装对象的'盒子',它负责创建、配置、组装你编写的 Java 对象(称为 Bean),并管理它们的生命周期。

IoC(控制反转) 是什么?

谁使用谁控制 => Spring 来控制。

是 Spring 的核心思想。传统代码中,对象自己控制依赖的创建(如 new Service());而 IoC 将控制权'反转'给容器——你只需声明依赖关系,容器自动注入所需对象。

例如:

// 传统方式:程序员自己创建依赖
UserService service = new UserService(); 

// IoC 方式:容器自动注入依赖
@Autowired
private UserService service;

简述 IoC 和 DI

控制反转(IoC)的核心思想是将对象的创建和管理权从传统的'谁使用谁控制'模式转变为由框架(如 Spring)统一管理。这种方式有效降低了组件间的耦合度,使系统更易于维护和扩展。

在 IoC 模式下,对象的生命周期完全由 Spring 容器管理,开发者无需手动实例化对象。依赖注入(DI)是 IoC 的一种实现方式,通过 DI,Spring 容器在运行时自动将依赖的对象注入到目标组件中,开发者只需声明依赖关系即可获得所需实例。

通过在类上面注释 @Component:告诉 Spring 将该对象的控制权转给 Spring。

在类中注明依赖之后注释 @Autowired:表明从 Spring 容器中拿出该对象并赋值给注明的依赖。

2. 详解 IoC

2.1 Bean 的存储

Spring 为了更好地完善 web 服务提供许多注解能够完成对象的存储,代替 @Component。

类注解: @Controller, @Service, @Repository, @Component, @Configuration

方法注解: @Bean

类注解:

@Controller public class UserController { public String sayHello(){ return "Hello World!"; } }

public static void main(String[] args) {
    // 应用上下文
    ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
    
    
       context.getBean(UserController.class);
    System.out.println(bean.sayHello());
}
// 通过 getBean 方法观察是否能拿到 UserController 对象
// 拿到说明通过@Controller,对象成功创建并且存储到 Spring 容器当中
UserController
bean
=

其他注解均可用同样的方法验证存储功能,如@Service:

@Service
public class UserService {
    public String doService(){ return "doService"; }
}

@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        UserService bean = context.getBean(UserService.class);
        System.out.println(bean);
    }
}

因此后续类注解同上验证不做过多重复解释,既然起到效果相似,为什么还要有这么多?原因是为了语义更加清晰,提高可读性,在对应的三层架构中使用对应的注解能更好的理解语义。

// 不好的实践:因为其余注解都是@Component 的衍生注解,因此都使用@Component @Component public class UserController { } // 这是控制器吗?服务吗?看不出来

@Component
public class UserService { } // 这是服务层吗?工具类吗?无法分辨

@Component public class UserDao { } // 这是数据访问层吗?不清楚

// 好的实践:语义明确 @Controller public class UserController { } // 明确是控制器,处理 HTTP 请求

@Service public class UserService { } // 明确是服务层,处理业务逻辑

@Repository public class UserDao { } // 明确是数据访问层

方法注解: 开发人员自己创建对象交给 Spring 去管理,用于解决同一个类需要多个对象的问题/创建第三方对象。

注意:需配合类注解使用。

// UserInfo 类
@AllArgsConstructor
@NoArgsConstructor
@Data
public class UserInfo {
    private String name;
    private int age;
}
// BeanConfig 类
@Configuration
public class BeanConfig {
    @Bean
    public UserInfo userInfo(){
        return new UserInfo("zhangsan", 12); // 自主创建对象
    }
}
@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        UserInfo bean1 = context.getBean(UserInfo.class);
        System.out.println(bean1);
    }
}

可以看到能够通过 @Bean 使 Spring 得到对 UserInfo 对象的控制权。

接下来解决多个对象创建的问题。

@Configuration
public class BeanConfig {
    // 创建两个 UserInfo 对象,方法名区分开
    @Bean
    public UserInfo userInfo(){
        return new UserInfo("zhangsan", 12);
    }
    
    @Bean
    public UserInfo userInfo2(){
        return new UserInfo("lisi", 16);
    }
}

@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        // 只靠类型无法区分对象,因此传入对象名作为参数
        UserInfo bean1 = context.getBean("userInfo", UserInfo.class);
        System.out.println(bean1);
        UserInfo bean2 = context.getBean("userInfo2", UserInfo.class);
        System.out.println(bean2);
    }
}

2.2 Bean Name 默认命名规则

(1) 类注解

  • 类名前两位为大写,如 USerinfo => bean name 为原类名
  • 其他情况 => bean name 为类名的小驼峰形式

(2) 方法注解

  • bean name 为方法名

2.3 扫描路径

默认情况下,Spring Boot只扫描启动类所在包及其子包。

但是可以通过 @ComponentScan(basePackages="") 来指定要扫描的路径。

2.4 @Autowired 和@Resource 区别

@Autowired 是 Spring提供的注解,根据类型进行注入。

@Resource 是 JDK提供的注解,根据名称注入。

2.5 @Autowired 查找 Bean 顺序

  1. 按类型查找 Bean
    • 根据依赖的类型(Class/Interface)在 Spring 容器中查找匹配的 Bean。
  2. 判断是否找到
    • 如果未找到 →报错
    • 如果找到 → 进入下一步判断。
  3. 是否配置了 @Qualifier 参数?
    • 如果已配置 @Qualifier → 按 @Qualifier指定的名称进一步筛选 Bean。
      • 如果找到唯一匹配 → 装配成功 ✅。
      • 如果未找到或仍匹配多个 → 抛异常 ❌。
    • 如果未配置 @Qualifier → 进入默认策略(按字段名匹配)。
  4. 字段名匹配
    • 匹配成功→装配成功
    • 未匹配→说明未指定名称,进入下一判断
  5. 是否只有一个 Bean
    • 如果只有一个 bean → 直接装配。

3. 详解 DI

依赖注入是一个过程,是 IoC 容器创建 Bean 的过程中提供所依赖的资源,这个资源就是对象。

Spring 提供了三种方式:

(1) 属性注入 (@Autowired)

@Component
public class UserService {
    // 方式 1:直接注入到属性
    @Autowired
    private UserRepository userRepository;
}

(2) 构造方法注入

@Service
public class UserService {
    // 使用 final,确保依赖不可变
    private final UserRepository userRepository;
    
    // 构造方法注入
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

注意:

  • 只有一个构造方法时默认执行这一个
  • 多个构造方法默认执行无参构造,若没有无参构造则报错
  • 可通过@Autowired 注解指定执行的构造方法

(3) Setter 注入

@Service
public class UserService {
    private UserService userService;
    
    // Setter 注入
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

Spring 三种依赖注入方式优缺点对比

对比维度构造方法注入Setter 注入属性注入
代码简洁性⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
可读性✅ 优秀(依赖一目了然)✅ 良好差(依赖关系隐藏)
不可变性✅ 支持 final(线程安全)不支持 final不支持 final
单元测试✅ 最容易(直接传参)✅ 容易困难(需要反射)
依赖完整性✅ 保证(对象创建即完整)不保证(可能部分注入)不保证
循环依赖检测✅ 启动时报错✅ Spring 可处理✅ Spring 可处理
可选依赖支持⭐ 有限(需 Optional 包装)✅ 优秀(required=false)✅ 支持
框架耦合度⭐ 低⭐ 低✅ 高(强依赖 Spring)
代码重构⭐⭐⭐⭐⭐⭐⭐✅ 最容易

目录

  1. 1. Spring 是什么?
  2. 2. 详解 IoC
  3. 2.1 Bean 的存储
  4. 2.2 Bean Name 默认命名规则
  5. 2.3 扫描路径
  6. 2.4 @Autowired 和@Resource 区别
  7. 2.5 @Autowired 查找 Bean 顺序
  8. 3. 详解 DI
  9. Spring 三种依赖注入方式优缺点对比
  • 💰 8折买阿里云服务器限时8折了解详情
  • 💰 8折买阿里云服务器限时8折购买
  • 🦞 5分钟部署阿里云小龙虾了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • C++类型约束实战精要
  • 联邦学习核心算法 FedAvg 原理及数据不均衡解决方案
  • Ollama 本地大模型运行指南
  • 前端设计与布局常用术语中英对照速查表
  • 基于 Dify 平台在 Flexus 云服务器上部署 AI Agent 实战
  • OpenClaw 接入摄像头实战:WSL2 下的视觉方案探索
  • HID Remapper 实现手柄到鼠标的精准转换
  • HTML 与 CSS 网页美化技巧
  • 自动化机器学习实战:从原理到企业级部署
  • 实时交互式数字人系统构建实战:从架构到代码实现
  • Jetpack Activity Results API 详解与源码分析
  • 机器人第一性原理:技术演进的本构逻辑与实现路径
  • 本地部署运行大模型指南
  • GitHub 国内镜像站汇总与 Git Clone 加速配置指南
  • VSCode Copilot 接入智谱 GLM-4 及任意大模型配置方案
  • Qwen3Guard-Gen-WEB 辅助人工内容安全复核
  • 基于 WebGIS 的身份证首位数字与六大区域可视化
  • 基于腾讯云服务器、宝塔面板与 Nginx 部署云图库项目
  • 深入解析 pb_ds 库:C++ 高性能数据结构实战指南
  • 区块链共识机制解析:PBFT、Tendermint 与 DAG 共识

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online