使用 Spring 实现控制反转和依赖注入

概述

在本文中,我们将介绍IoC(控制反转)和DI(依赖注入)的概念,以及如何在Spring框架中实现它们。

什么是控制反转?

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

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

这种架构的优点是:

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

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

什么是依赖注入?

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

将对象与其他对象连接或将对象"注入"到其他对象中是由汇编程序而不是对象本身完成的。

下面是在传统编程中创建对象依赖关系的方法:

ini 复制代码
publicclassStore {
private Item item;

publicStore() {
        item =newItemImpl1();
    }
}Copy

在上面的示例中,我们需要在*Store

类本身中实例化

Item*接口的实现。

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

ini 复制代码
publicclassStore {
private Item item;
publicStore(Item item) {
        this.item = item;
    }
}Copy

在接下来的几节中,我们将看看如何通过元数据提供

Item

的实现。

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

Spring IoC容器

IoC容器是实现IoC的框架的常见特征。

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

Spring框架提供了*ApplicationContext

接口的几个实现:

ClassPathXmlApplicationContext

FileSystemXmlApplicationContext

用于独立应用程序,以及

WebApplicationContext*用于Web应用程序。

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

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

ini 复制代码
ApplicationContext context
  =newClassPathXmlApplicationContext("applicationContext.xml");Copy

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

Spring中,可以通过构造函数、setter或字段来进行依赖注入。

基于构造函数的依赖注入

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

Spring通过类型解决每个参数,然后按属性名称和索引进行消歧。让我们看看使用注释配置bean及其依赖项的配置:

scss 复制代码
@Configuration
publicclassAppConfig {

    @Bean
public Itemitem1() {
returnnewItemImpl1();
    }

    @Bean
public Storestore() {
returnnewStore(item1());
    }
}Copy

*@Configuration*注释表示该类是bean定义的源。我们也可以将其添加到多个配置类中。

我们在方法上使用@Bean注释来定义bean。如果我们没有指定自定义名称,则bean名称将默认为方法名称。

对于默认的*singleton

范围的beanSpring首先检查是否已存在缓存的bean实例,仅在不存在时创建新实例。如果我们使用

prototype*范围,则容器为每个方法调用返回一个新的bean实例。

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

ini 复制代码
<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>Copy

基于setter的依赖注入

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

ini 复制代码
@Bean
public Storestore() {
Store store =newStore();
    store.setItem(item1());
return store;
}Copy

我们也可以使用XML进行相同的bean配置:

ini 复制代码
<bean id="store" class="org.baeldung.store.Store">
    <property name="item" ref="item1" />
</bean>Copy

我们可以将构造函数和setter类型的注入结合在同一个bean中。Spring文档建议将基于构造函数的注入用于必需的依赖项,将基于setter的注入用于可选的依赖项。

基于字段的依赖注入

在基于字段的DI的情况下,我们可以通过带有@Autowired注释的注释将依赖项注入其中:

java 复制代码
publicclassStore {
    @Autowired
private Item item;
}Copy

在构造*Store

对象时,如果没有构造函数或setter方法将

Item

bean注入其中,容器将使用反射将

Item

注入

Store*中。

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

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

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

自动装配依赖项

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

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

  • no:默认值 - 这意味着不使用自动装配,我们必须显式地命名依赖项。
  • byName:按属性名称进行自动装配,因此Spring将查找与需要设置的属性同名的bean
  • byType:类似于按名称进行自动装配,仅基于属性的类型。这意味着Spring将查找具有相同类型的属性来设置的bean。如果有多个bean具有该类型,则框架会抛出异常。
  • constructor:基于构造函数参数进行自动装配,意味着Spring将查找具有与构造函数参数相同类型的bean

例如,让我们通过类型创建具有依赖项*item

store*bean

typescript 复制代码
public class AppConfig {
    @Bean
    public Item item() {
        return new ItemImpl1();
    }

    @Bean(autowire = Autowire.BY_TYPE)
    public Store store() {
        return new Store();
    }
}

请注意,自Spring 5.1起,*autowire*属性已弃用。

我们还可以使用@Autowired注释按类型注入bean

kotlin 复制代码
public class Store {

    @Autowired
    private Item item;
}

如果存在相同类型的多个bean,则可以使用@Qualifier注释按名称引用bean

less 复制代码
public class Store {

    @Autowired
    @Qualifier("item1")
    private Item item;
}

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

bash 复制代码
<bean id="store" class="org.baeldung.store.Store" autowire="byType"> </bean>

接下来,让我们通过XML按名称将名为*item

bean注入到

store

bean

item*属性中:

ini 复制代码
<bean id="item" class="org.baeldung.store.ItemImpl1" />

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

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

惰性初始化的bean

默认情况下,容器在初始化期间创建和配置所有单例bean。为了避免这种情况,我们可以在bean配置上使用值为*true

lazy-init*属性:

csharp 复制代码
<bean id="item1" class="org.baeldung.store.ItemImpl1" lazy-init="true" />

因此,只有在第一次请求它时,才会初始化*item1*bean,而不是在启动时。这样做的优点是初始化时间更快,但缺点是我们在bean被请求之后才会发现任何配置错误,这可能是应用程序已运行数小时甚至数天之后。

结论

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

最后

为了方便其他设备和平台的小伙伴观看往期文章,链接奉上:

公众号搜索Let us Coding 知乎 开源中国 CSDN 思否 掘金 InfoQ 简书 博客园 慕课 51CTO helloworld 腾讯开发者社区 阿里开发者社区

看完如果觉得有帮助,欢迎点赞、收藏关注

相关推荐
卑微的Coder2 小时前
Redis Set集合命令、内部编码及应用场景(详细)
java·数据库·redis
CrissChan2 小时前
Pycharm 函数注释
java·前端·pycharm
启航挨踢3 小时前
java学习电子书推荐
java
wgslucky3 小时前
Dubbo报错:module java.base does not “opens java.lang“ to unnamed module
java·开发语言·dubbo
DougLiang4 小时前
关于easyexcel动态下拉选问题处理
java·开发语言
mochensage4 小时前
C++信息学竞赛中常用函数的一般用法
java·c++·算法
烛阴5 小时前
bignumber.js深度解析:驾驭任意精度计算的终极武器
前端·javascript·后端
计蒙不吃鱼5 小时前
一篇文章实现Android图片拼接并保存至相册
android·java·前端
服务端技术栈5 小时前
电商营销系统中的幂等性设计:从抽奖积分发放谈起
后端
小海编码日记5 小时前
Java八股-JVM & GC
java