Constructor Dependency Injection in Spring – Spring中的构造函数依赖性注入

最后修改: 2016年 11月 23日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Arguably one of the most important development principles of modern software design is Dependency Injection (DI), which quite naturally flows out of another critically important principle: Modularity.

可以说,现代软件设计中最重要的开发原则之一是依赖注入(DI),它很自然地从另一个至关重要的原则中流出。模块化(Modularity)。

This quick tutorial will explore a specific type of DI technique within Spring called Constructor-Based Dependency Injection, which simply put, means that we pass the required components into a class at the time of instantiation.

这个快速教程将探讨Spring中的一种特定类型的DI技术,称为基于构造物的依赖注入,简单地说,这意味着我们在实例化时将所需的组件传入一个类。

To get started, we need to import the spring-context dependency in our pom.xml:

为了开始工作,我们需要在pom.xml中导入spring-context依赖项。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

Then we need to set up a Configuration file. This file can be either a POJO or an XML file, based on preference.

然后我们需要设置一个Configuration文件。这个文件可以是一个POJO文件,也可以是一个XML文件,根据偏好而定。

2. Annotation Based Configuration

2.基于注释的配置

Java configuration files look similar to Java objects with some additional annotations:

Java配置文件看起来类似于带有一些额外注解的Java对象。

@Configuration
@ComponentScan("com.baeldung.constructordi")
public class Config {

    @Bean
    public Engine engine() {
        return new Engine("v8", 5);
    }

    @Bean
    public Transmission transmission() {
        return new Transmission("sliding");
    }
}

Here we’re using annotations to notify Spring runtime that this class provides bean definitions (@Bean annotation), and that the package com.baeldung.spring needs to perform a context scan for additional beans. Next, we define a Car class:

这里我们使用注解来通知Spring运行时,这个类提供了Bean定义(@Bean注解),并且包com.baeldung.spring需要执行上下文扫描以获取更多的Bean。接下来,我们定义一个Car类。

@Component
public class Car {

    @Autowired
    public Car(Engine engine, Transmission transmission) {
        this.engine = engine;
        this.transmission = transmission;
    }
}

Spring will encounter our Car class while doing a package scan, and will initialize its instance by calling the @Autowired annotated constructor.

Spring将在进行包扫描时遇到我们的Car 类,并将通过调用@Autowired 注解的构造函数来初始化其实例。

By calling the @Bean annotated methods of the Config class, we will obtain instances of Engine and Transmission. Finally, we need to bootstrap an ApplicationContext using our POJO configuration:

通过调用@Bean类的注释方法,我们将获得Engine 和 Transmission的实例。最后,我们需要使用我们的POJO配置来引导一个ApplicationContext

ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Car car = context.getBean(Car.class);

3. Implicit Constructor Injection

3.隐式构造函数注入

As of Spring 4.3, classes with a single constructor can omit the @Autowired annotation. This is a nice little bit of convenience and boilerplate removal.

从Spring 4.3开始,只有一个构造函数的类可以省略@Autowired 注释。这是一个很好的小便利和模板删除。

On top of that, also starting with 4.3, we can leverage the constructor-based injection in @Configuration annotated classes. In addition, if such a class has only one constructor, we can omit the @Autowired annotation as well.

除此之外,从 4.3 开始,我们可以在 @Configuration 注释的类中利用基于构造器的注入。此外,如果这样的类只有一个构造函数,我们也可以省略@Autowired 注释。

4. XML Based Configuration

4.基于XML的配置

Another way to configure Spring runtime with constructor-based dependency injection is to use an XML configuration file:

用基于构造函数的依赖注入配置Spring运行时的另一种方法是使用XML配置文件。

<bean id="toyota" class="com.baeldung.constructordi.domain.Car">
    <constructor-arg index="0" ref="engine"/>
    <constructor-arg index="1" ref="transmission"/>
</bean>

<bean id="engine" class="com.baeldung.constructordi.domain.Engine">
    <constructor-arg index="0" value="v4"/>
    <constructor-arg index="1" value="2"/>
</bean>

<bean id="transmission" class="com.baeldung.constructordi.domain.Transmission">
    <constructor-arg value="sliding"/>
</bean>

Note that constructor-arg can accept a literal value or a reference to another bean, and that an optional explicit index and type can be provided. We can use Type and index attributes to resolve ambiguity (for example if a constructor takes multiple arguments of the same type).

请注意,构造函数-arg可以接受一个字面值或对另一个 bean 的引用,并且可以提供一个可选的显式 indextype。我们可以使用Typeindex属性来解决歧义(例如,如果一个构造函数接受相同类型的多个参数)。

name attribute could also be used for xml to java variable matching, but then your code must be compiled with debug flag on.

nameattribute也可以用于xml到java变量的匹配,但这样你的代码必须在编译时打开调试标志。

In this case, we need to bootstrap our Spring application context using ClassPathXmlApplicationContext:

在这种情况下,我们需要使用ClassPathXmlApplicationContext来引导我们的Spring应用上下文。

ApplicationContext context = new ClassPathXmlApplicationContext("baeldung.xml");
Car car = context.getBean(Car.class);

5. Pros and Cons

5.优点和缺点

Constructor injection has a few advantages compared to field injection.

构造函数注入与字段注入相比有几个优点。

The first benefit is testability. Suppose we’re going to unit test a Spring bean that uses field injection:

第一个好处是可测试性。假设我们要对一个使用字段注入的Spring Bean进行单元测试。

public class UserService {
    
    @Autowired 
    private UserRepository userRepository;
}

During the construction of a UserService instance, we can’t initialize the userRepository state. The only way to achieve this is through the Reflection API, which completely breaks encapsulation. Also, the resulting code will be less safe compared to a simple constructor call.

在构建UserService实例期间,我们不能初始化userRepository状态。实现这一目标的唯一方法是通过Reflection API,这完全破坏了封装。而且,与简单的构造函数调用相比,所产生的代码的安全性会降低。

Additionally, with field injection, we can’t enforce class-level invariants, so it’s possible to have a UserService instance without a properly initialized userRepository. Therefore, we may experience random NullPointerExceptions here and there. Also, with constructor injection, it’s easier to build immutable components.

此外,有了字段注入,我们无法执行类级的不变性,s所以有可能有一个UserService实例而没有一个正确初始化的userRepository。因此,我们可能会随机遇到NullPointerExceptions在这里和那里。另外,有了构造函数注入,构建不可变的组件就更容易了。

Moreover, using constructors to create object instances is more natural from the OOP standpoint.

此外,从OOP的角度来看,使用构造函数来创建对象实例更加自然。

On the other hand, the main disadvantage of constructor injection is its verbosity, especially when a bean has a handful of dependencies. Sometimes it can be a blessing in disguise, as we may try harder to keep the number of dependencies minimal.

另一方面,构造函数注入的主要缺点是它的冗长性,特别是当一个Bean有少量的依赖关系时。有时,这可能是一种变相的祝福,因为我们可能会更努力地保持最小的依赖关系数量。

6. Conclusion

6.结论

This brief article has showcased the basics of two distinct ways to use Constructor-Based Dependency Injection using the Spring framework.

这篇简短的文章展示了使用Spring框架使用基于构架的依赖注入的两种不同方式的基础知识。

The full implementation of this article can be found over on GitHub.

本文的完整实现可以在GitHub上找到over