Contents

Spring的构造函数依赖注入

1.简介

可以说,现代软件设计最重要的开发原则之一是依赖注入 (DI),它很自然地源于另一个至关重要的原则:模块化。 这个快速教程将探讨 Spring 中一种特定类型的 DI 技术,称为基于构造函数的依赖注入,简单地说,意味着我们在实例化时将所需的组件传递给一个类。 首先,我们需要在pom.xml中导入spring-context依赖项:

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

然后我们需要设置一个配置文件。根据偏好,该文件可以是 POJO 或 XML 文件。

2. 基于注解的配置

Java 配置文件看起来类似于带有一些附加注释的 Java 对象:

@Configuration
@ComponentScan("com.blogdemo.constructordi")
public class Config {
    @Bean
    public Engine engine() {
        return new Engine("v8", 5);
    }
    @Bean
    public Transmission transmission() {
        return new Transmission("sliding");
    }
}

在这里,我们使用注解来通知 Spring 运行时这个类提供了 bean 定义(@Bean注解),并且包com.blogdemo.spring需要执行上下文扫描以查找其他 bean。接下来,我们定义一个Car类:

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

Spring在进行包扫描时会遇到我们的Car类,并会通过调用*@Autowired带注释的构造函数来初始化它的实例。 通过调用Config类的@Bean注解方法,我们将获得Engine 和 Transmission的实例。最后,我们需要使用我们的 POJO 配置来引导ApplicationContext* :

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

3. 隐式构造函数注入

从 Spring 4.3 开始,具有单个构造函数的类可以省略*@Autowired注释。这是一个很好的便利和样板删除。 最重要的是,同样从 4.3 开始,我们可以在@Configuration注释类中利用基于构造函数的注入。另外,如果这样的类只有一个构造函数,我们也可以省略@Autowired*注解。

4. 基于 XML 的配置

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

<bean id="toyota" class="com.blogdemo.constructordi.domain.Car">
    <constructor-arg index="0" ref="engine"/>
    <constructor-arg index="1" ref="transmission"/>
</bean>
<bean id="engine" class="com.blogdemo.constructordi.domain.Engine">
    <constructor-arg index="0" value="v4"/>
    <constructor-arg index="1" value="2"/>
</bean>
<bean id="transmission" class="com.blogdemo.constructordi.domain.Transmission">
    <constructor-arg value="sliding"/>
</bean>

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

name属性也可以用于 xml 到 java 变量的匹配,但是你的代码必须在编译时使用调试标志。

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

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

5. 优点和缺点

与字段注入相比,构造函数注入有一些优点。 **第一个好处是可测试性。**假设我们要对一个使用字段注入的 Spring bean 进行单元测试:

public class UserService {

    @Autowired
    private UserRepository userRepository;
}

UserService实例的构建过程中,我们无法初始化userRepository状态。实现这一点的唯一方法是通过完全打破封装的反射 API 。此外,与简单的构造函数调用相比,生成的代码将不太安全。 此外,通过字段注入,我们无法强制执行类级别的常量,因此可能会在没有正确初始化userRepository的情况下拥有 UserService实例。因此,我们可能会在这里和那里遇到随机的NullPointerException。此外,使用构造函数注入,更容易构建不可变组件。 此外,从 OOP 的角度来看,使用构造函数创建对象实例更为自然。 另一方面,构造函数注入的主要缺点是其冗长,尤其是当 bean 具有少量依赖项时。有时这可能是因祸得福,因为我们可能会更加努力地将依赖项的数量保持在最低限度。