Spring 控制反转与依赖注入:从玄学编程到科学管理

简介: 在传统开发中,手动`new`对象导致紧耦合、难以维护和测试。控制反转(IoC)将对象创建交给框架,实现解耦。Spring通过IOC容器自动管理对象生命周期,开发者只需声明依赖,无需关心创建细节。依赖注入(DI)是IoC的具体实现方式,支持构造器、Setter和字段注入。构造器注入推荐使用,保证依赖不可变且易于测试。对于多个同类型Bean,可用`@Qualifier`或`@Primary`解决冲突。此外,Spring还支持依赖查找(DL),开发者主动从容器获取Bean,适用于动态场景,但侵入性强。掌握IoC与DI,有助于构建灵活、可维护的Spring应用。

传统开发:失控的"new"地狱

// 传统开发方式(紧耦合)
public class OrderService {
   
    // 硬编码依赖
    private PaymentService payment = new AlipayService();
    private Logger logger = new FileLogger("/tmp/log");

    void pay() {
   
        payment.process();  // 想换成微信支付?改代码重编译!
    }
}

在传统模式开发时。我们想要使用一个对象时,都需要手动new一个对象,这个对象什么时候new,在哪里new,怎么new,都是开发者自己决定的,这就要求开发者对要使用的对象的内部细节非常的了解。要是这个对象是自己写的还好。要是别人写的,那我们在使用一个对像前还需要先去了解这个对象的整个依赖。

同时一个对象可能还依赖其他的对象,我们可能需要一个对象A,但对象A又依赖对象B、对象C 那么我们就又需要创建对象B和对象C,同时对象B和对象C可能又依赖其他的对象。为了拿到对象A,我们可能要额外的创建好多其他的对象。工作量逐渐开始失控。

痛点清单

  • 🔧 改需求要动源代码
  • 🧪 没法做单元测试
  • 🕸️ 依赖关系像蜘蛛网

于是聪明的你想到了把new对象的权利丢给其他人,我想要的时候直接找别人要就好了。
我们把你的这种想法起了一个好听的名字叫控制反转(IOC)

控制反转(IOC):把"new"的权力上交

定义
控制反转是一种设计原则,将对象的创建、依赖管理权从程序员转移给框架/容器,实现解耦

上面我们说到了我们想把new的权利外包出去,但外包给谁呢?这是一个Spring 的框架接下了这个活,他、它把所有的对象都放在了一个叫IOC容器的地方,我们需要使用对象的话直接告诉它需要使用哪个对象就好了,于是我们之前的代码变成了这样

// 交出控制权后的代码 
public class OrderService {
    
    @Autowired // 声明需要什么 
    private PaymentService payment; 
    @Autowired 
    private Logger logger; // 不再关心具体实现 
}

权力转移示意图
16a8cbc95b5f8c9c0bed1631edf39c6a_MD5.jpeg

传统方式 IoC方式
开发者手动new对象 容器自动创建和管理对象
直接调用依赖对象 依赖由容器注入
高耦合(如A a = new A() 低耦合(@Autowired private A a

本质:好莱坞原则——"别找我们,我们会找你"

Spring实现
通过ApplicationContext管理所有Bean的生命周期。


依赖注入(Dependency Injection,)

定义

依赖注入(Dependency Injection,DI)是一种设计模式,是IOC的具体实现方式,由容器动态地将依赖关系注入到对象中。

在 Spring 接下了对象创建的控制权后,也遇到了对象间互相依赖的问题。于是Spring要求对象自己向Spring声明创建时需要依赖的对象,这些对象Spring也从IOC容器中获取,Spring将这种做法叫做依赖注入

核心思想

  • 谁负责创建依赖?容器(Spring IoC 容器)
  • 谁决定依赖关系?配置(注解、XML、Java Config)
  • 对象如何获取依赖?被动接收(通过构造函数、Setter 或字段注入)

依赖注入的三种方式

Spring 提供了 3 种主要的依赖注入方式:

1. 构造器注入(Constructor Injection)

推荐方式!(Spring 官方首选)

public class OrderService {
   
    private final PaymentService paymentService;

    // 构造器注入(Spring 4.3+ 可自动装配,无需 @Autowired)
    public OrderService(PaymentService paymentService) {
   
        this.paymentService = paymentService;
    }
}

优点

  • 强制依赖:对象创建时必须提供所有依赖,避免了 NullPointerException
  • 不可变依赖final 关键字确保依赖不会被修改。
  • 易于测试:可以直接传入 Mock 对象进行单元测试。

缺点

  • 依赖较多时,构造函数会很长(但可以使用 Lombok @RequiredArgsConstructor 简化)。

2. Setter 注入(Setter Injection)

适用于可选依赖或需要动态变更依赖的场景。

public class OrderService {
   
    private PaymentService paymentService;

    // Setter 注入(@Autowired 可省略)
    @Autowired
    public void setPaymentService(PaymentService paymentService) {
   
        this.paymentService = paymentService;
    }
}

优点

  • 允许动态替换依赖(如切换支付方式)。
  • 适用于可选依赖(可以不调用 Setter)。

缺点

  • 依赖可能处于半初始化状态(对象创建完成但依赖未设置)。
  • 不能保证依赖非空(除非手动检查)。

3. 字段注入(Field Injection)

最方便但不推荐(仅适用于快速原型开发)。

public class OrderService {
   
    @Autowired  // 直接注入字段
    private PaymentService paymentService;
}

优点

  • 代码简洁,写起来快。

缺点

  • 破坏封装性:字段必须是 public 或提供反射访问权限。
  • 难以测试:必须用 Spring 容器或反射才能注入 Mock。
  • 不能声明 final:依赖可能被修改。

依赖注入的进阶技巧

最佳实战

  1. 优先使用构造器注入

    • 保证依赖不可变
    • 避免NPE(依赖必须非空)
  2. 避免字段注入

    • 不利于测试(需反射设置字段)
    • 隐藏依赖关系
  3. 结合@Qualifier解决歧义

    @Autowired
    @Qualifier("mysqlRepository")
    private UserRepository repository;
    
  4. 可选依赖使用@Autowired(required=false)

    处理多个同类型 Bean

如果多个 PaymentService 实现类存在,Spring 会报 NoUniqueBeanDefinitionException

解决方案

// 方式1:@Qualifier 指定 Bean 名称
@Autowired
@Qualifier("alipayService")  // 指定注入的 Bean ID
private PaymentService paymentService;

// 方式2:@Primary 标记默认 Bean
@Bean
@Primary
public PaymentService wechatPayService() {
   
    return new WeChatPayService();
}

可选依赖(允许 null)

// 方式1:@Autowired(required = false)
@Autowired(required = false)
private Optional<Logger> logger;  // 如果没有 Logger Bean,logger = Optional.empty()

// 方式2:Java 8 Optional
@Autowired
public void setLogger(Optional<Logger> logger) {
   
    logger.ifPresent(l -> this.logger = l);
}

3. 集合类型自动注入

Spring 会自动收集所有匹配类型的 Bean:

@Autowired
private List<PaymentService> paymentServices;  // 注入所有 PaymentService 实现类

补充:依赖查找(Dependency Lookup, DL)

定义

依赖查找是主动从容器中获取依赖对象的方式,开发者需要显式调用 API(如 getBean())来查找依赖。

在通过Spring 在IOC容器中获取对象时,除了依赖注入的方式,还提供了一种;就是需要什么对象,你自己去找,把主动权又交还了一部分给开发者。还是需要开发者主动跟Spring容器中去找对象,但是不需要创建,创建对象的工作还是由Spring完成。
这也是控制反转的一种实现方式
d1701bbad45de6d3773c1623b023ed82_MD5.jpeg

1. 典型实现方式

// 使用 ApplicationContext 查找 Bean
ApplicationContext context = ...;  
UserService userService = context.getBean("userService", UserService.class);

特点

  • 控制权在开发者手中:需要手动调用容器 API 获取依赖。
  • 强依赖容器:代码中必须引入 Spring 相关类(如 ApplicationContext)。
  • 灵活性高:可以动态决定获取哪个 Bean。

2. 适用场景

  • 动态决策依赖(如根据配置选择不同的实现)。
  • 在非 Spring 管理的类中获取 Bean(如工具类)。

3. 优缺点

优点

  • 灵活控制依赖获取时机。
  • 适合动态场景(如运行时切换实现)。

缺点

  • 侵入性强:代码依赖 Spring API。
  • 难以测试:必须启动 Spring 容器才能测试。
  • 容易出错:手动查找可能触发 NoSuchBeanDefinitionException

对比:依赖查找 vs依赖注入

特性 依赖查找(DL) 依赖注入(DI)
控制方式 主动查找(getBean() 被动接收(@Autowired
代码侵入性 高(依赖 Spring API) 低(仅需注解)
灵活性 高(可动态获取 Bean) 较低(启动时确定)
可测试性 差(需容器环境) 好(可直接 Mock 注入)
适用场景 动态依赖、工具类 大多数业务逻辑
相关文章
|
2月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
403 0
|
1月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
3月前
|
人工智能 监控 安全
Spring AOP切面编程颠覆传统!3大核心注解+5种通知类型,让业务代码纯净如初
本文介绍了AOP(面向切面编程)的基本概念、优势及其在Spring Boot中的使用。AOP作为OOP的补充,通过将横切关注点(如日志、安全、事务等)与业务逻辑分离,实现代码解耦,提升模块化程度、可维护性和灵活性。文章详细讲解了Spring AOP的核心概念,包括切面、切点、通知等,并提供了在Spring Boot中实现AOP的具体步骤和代码示例。此外,还列举了AOP在日志记录、性能监控、事务管理和安全控制等场景中的实际应用。通过本文,开发者可以快速掌握AOP编程思想及其实践技巧。
|
10月前
|
人工智能 Java API
阿里云工程师跟通义灵码结伴编程, 用Spring AI Alibaba来开发 AI 答疑助手
本次分享的主题是阿里云工程师跟通义灵码结伴编程, 用Spring AI Alibaba来开发 AI 答疑助手,由阿里云两位工程师分享。
507 2
阿里云工程师跟通义灵码结伴编程, 用Spring AI Alibaba来开发 AI 答疑助手
|
10月前
|
XML 监控 前端开发
Spring Boot中的WebFlux编程模型
Spring WebFlux 是 Spring Framework 5 引入的响应式编程模型,基于 Reactor 框架,支持非阻塞异步编程,适用于高并发和 I/O 密集型应用。本文介绍 WebFlux 的原理、优势及在 Spring Boot 中的应用,包括添加依赖、编写响应式控制器和服务层实现。WebFlux 提供高性能、快速响应和资源节省等优点,适合现代 Web 应用开发。
1179 15
|
11月前
|
Java Spring
一键注入 Spring 成员变量,顺序编程
介绍了一款针对Spring框架开发的插件,旨在解决开发中频繁滚动查找成员变量注入位置的问题。通过一键操作(如Ctrl+1),该插件可自动在类顶部添加`@Autowired`注解及其成员变量声明,同时保持光标位置不变,有效提升开发效率和代码编写流畅度。适用于IntelliJ IDEA 2023及以上版本。
162 2
一键注入 Spring 成员变量,顺序编程
|
Java 数据库连接 Spring
【2021Spring编程实战笔记】Spring开发分享~(下)
【2021Spring编程实战笔记】Spring开发分享~(下)
142 1
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
XML Java 数据库连接
【2020Spring编程实战笔记】Spring开发分享~(上)
【2020Spring编程实战笔记】Spring开发分享~
134 0