控制反转和依赖注入到底是个啥

我们将介绍 IoC(控制反转)和 DI(依赖注入)的概念,并看看它们是到底在 Spring 框架中实现的

   

  • Spring框架管理这些Bean的创建工作,即由用户管理Bean转变为框架管理Bean,这个就叫控制反转 - Inversion of Control (IoC)
  • Spring 框架托管创建的Bean放在哪里呢? 这便是IoC Container;
  • Spring 框架为了更好让用户配置Bean,必然会引入不同方式来配置Bean? 这便是xml配置,Java配置,注解配置等支持
  • Spring 框架既然接管了Bean的生成,必然需要管理整个Bean的生命周期等;
  • 应用程序代码从Ioc Container中获取依赖的Bean,注入到应用程序中,这个过程叫 依赖注入(Dependency Injection,DI) ; 所以说控制反转是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是IoC是设计思想,DI是实现方式
  • 在依赖注入时,有哪些方式呢?这就是构造器方式,@Autowired, @Resource, @Qualifier... 同时Bean之间存在依赖(可能存在先后顺序问题,以及循环依赖问题等)

著作权归@pdai所有 原文链接:https://pdai.tech/md/spring/spring-x-framework-helloworld.html

     

1. 什么是控制反转?

Spring框架管理这些Bean的创建工作,即由用户管理Bean转变为框架管理Bean,这个就叫控制反转 - Inversion of Control (IoC)

控制反转是软件工程中的一项原则,它将对象或程序部分的控制权转移到容器或框架。我们最常在面向对象编程的上下文中使用它。

与我们的自定义代码调用库的传统编程相比,IoC 使框架能够控制程序流并调用我们的自定义代码。为了实现这一点,框架使用了带有附加行为的抽象。如果我们想添加我们自己的行为,我们需要扩展框架的类或插入我们自己的类。

这种架构的优点是:

  • 将任务的执行与其实现分离
  • 更容易在不同的实现之间切换
  • 程序的模块化程度更高
  • 通过隔离组件或模拟其依赖项,并允许组件通过合约进行通信,从而更轻松地测试程序

我们可以通过各种机制实现控制反转,例如:策略设计模式、服务定位器模式、工厂模式和依赖注入(DI)。

2. 什么是依赖注入?

依赖注入是一种我们可以用来实现 IoC 的模式,其中被反转的控制是设置对象的依赖关系。

将对象与其他对象连接,或将对象“注入”到其他对象中,是由装配程序完成的,而不是由对象本身完成的。

以下是我们如何在传统编程中创建对象依赖项:

public class Store {
    private Item item;
 
    public Store() {
        item = new ItemImpl1();    
    }
}

在上面的示例中,我们需要在Store类本身中实例化Item接口的实现。

通过使用 DI,我们可以重写示例而无需指定我们想要的Item的实现:

public class Store {
    private Item item;
    public Store(Item item) {
        this.item = item;
    }
}

在接下来的部分中,我们将研究如何通过元数据提供Item的实现。

IoC 和 DI 都是简单的概念,但它们对我们构建系统的方式有着深刻的影响,因此非常值得充分理解。

3. Spring IoC 容器

IoC 容器是实现 IoC 的框架的共同特征。

在 Spring 框架中,接口 ApplicationContext代表了 IoC 容器。Spring 容器负责实例化、配置和组装称为beans 的对象,以及管理它们的生命周期。

Spring 框架提供了ApplicationContext接口的几种实现:ClassPathXmlApplicationContextFileSystemXmlApplicationContext用于独立应用程序,以及WebApplicationContext用于 Web 应用程序。

为了组装 bean,容器使用配置元数据,可以是 XML 配置或注解的形式。

这是手动实例化容器的一种方法:

ApplicationContext context
  = new ClassPathXmlApplicationContext("applicationContext.xml");

要在上面的示例中设置item属性,我们可以使用元数据。然后容器将读取此元数据并在运行时使用它来组装 bean。

Spring 中的依赖注入可以通过构造函数、setter 或字段来完成。

4. 基于构造函数的依赖注入

基于构造函数的依赖注入的情况下,容器将调用带有参数的构造函数,每个参数代表我们要设置的依赖项。

Spring 主要按类型解析每个参数,然后是属性名称和用于消除歧义的索引。让我们使用注解来查看 bean 及其依赖项的配置:

@Configuration
public class AppConfig {

    @Bean
    public Item item1() {
        return new ItemImpl1();
    }

    @Bean
    public Store store() {
        return new Store(item1());
    }
}

@Configuration注释表明类是bean定义的来源。我们还可以将其添加到多个配置类中。

我们在方法上使用@Bean注释来定义一个 bean。如果我们不指定自定义名称,那么 bean 名称将默认为方法名称。

对于具有默认单例作用域的 bean ,Spring 首先检查该 bean 的缓存实例是否已经存在,如果不存在,则仅创建一个新实例。如果我们使用原型作用域,容器会为每个方法调用返回一个新的 bean 实例。

创建 bean 配置的另一种方法是通过 XML 配置:

<bean id="item1" class="org.baeldung.store.ItemImpl1" /> 
<bean id="store" class="org.baeldung.store.Store"> 
    <constructor-arg type="ItemImpl1" index="0" name="item" ref="item1" /> 
</bean>

5. 基于Setter的依赖注入

对于基于 setter 的 DI,容器会在调用无参构造函数或无参静态工厂方法实例化 bean 后调用我们类的 setter 方法。让我们使用注释创建此配置:

@Bean
public Store store() {
    Store store = new Store();
    store.setItem(item1());
    return store;
}

我们还可以将 XML 用于 bean 的相同配置:

<bean id="store" class="org.baeldung.store.Store">
    <property name="item" ref="item1" />
</bean>

我们可以为同一个 bean 组合基于构造函数和基于 setter 的注入类型。Spring 文档建议对强制依赖项使用基于构造函数的注入,对可选依赖项使用基于 setter 的注入。

6. 基于字段的依赖注入

在基于字段的 DI 的情况下,我们可以通过使用@Autowired注释标记它们来注入依赖项:

public class Store {
    @Autowired
    private Item item; 
}

在构建Store对象时,如果没有构造器或 setter 方法来注入Item bean,容器将使用反射将Item注入Store

我们也可以使用XML 配置来实现这一点。

这种方法可能看起来更简单、更干净,但我们不建议使用它,因为它有一些缺点,例如:

  • 此方法使用反射来注入依赖项,这比基于构造函数或基于 setter 的注入成本更高。
  • 使用这种方法继续添加多个依赖项真的很容易。如果我们使用构造函数注入,有多个参数会让我们认为该类做了不止一件事,这可能违反单一职责原则。

更多关于@Autowired注释的信息可以在Wiring In Spring文章中找到。

7. 自动装配依赖

Wiring允许 Spring 容器通过检查已定义的 bean 来自动解决协作 bean 之间的依赖关系。

使用 XML 配置自动装配 bean 有四种模式:

  • no默认值——这意味着 bean 没有使用自动装配,我们必须明确命名依赖项。
  • byName自动装配是基于属性的名称完成的,因此 Spring 会寻找一个与需要设置的属性同名的 bean。
  • byType类似于byName自动装配,仅基于属性的类型。这意味着 Spring 将寻找一个与要设置的属性类型相同的 bean。如果有多个该类型的 bean,框架会抛出异常。
  • 构造函数自动装配是基于构造函数参数完成的,这意味着 Spring 将查找与构造函数参数具有相同类型的 bean。

例如,让我们将上面定义的item1 bean自动装配到store bean 中:

@Bean(autowire = Autowire.BY_TYPE)
public class Store {
    
    private Item item;

    public setItem(Item item){
        this.item = item;    
    }
}

我们还可以使用@Autowired注释注入 bean,以便按类型进行自动装配:

public class Store {
    
    @Autowired
    private Item item;
}

如果有多个相同类型的 bean,我们可以使用@Qualifier注释通过名称引用 bean:

public class Store {
    
    @Autowired
    @Qualifier("item1")
    private Item item;
}

现在让我们通过 XML 配置按类型自动装配 bean:

<bean id="store" class="org.baeldung.store.Store" autowire="byType"> </bean>

接下来,让我们通过XML按名称将一个名为item的bean注入到store bean的item属性中:

<bean id="item" class="org.baeldung.store.ItemImpl1" />

<bean id="store" class="org.baeldung.store.Store" autowire="byName">
</bean>

我们还可以通过构造函数参数或 setter 显式定义依赖项来覆盖自动装配。

8. 延迟初始化的 Bean

默认情况下,容器在初始化期间创建和配置所有单例 bean。为了避免这种情况,我们可以在 bean 配置中使用值为truelazy-init属性:

<bean id="item1" class="org.baeldung.store.ItemImpl1" lazy-init="true" />

因此,item1 bean 只会在第一次请求时初始化,而不是在启动时初始化。这样做的好处是初始化时间更快,但代价是我们不会在请求 bean 之前发现任何配置错误,这可能是应用程序已经运行后的几个小时甚至几天。

9. 结论

在本文中,我们介绍了控制反转和依赖注入的概念,并在 Spring 框架中进行了举例说明。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小道士写程序

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值