Spring中的构造依赖注入

2023/05/13

1. 概述

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

本文将探讨Spring中一种特定类型的DI技术,称为基于构造函数的依赖注入,简单地说,就是我们在实例化时将所需的组件传递给一个类。

2. 基于注解的配置


@Configuration
@ComponentScan("cn.tuyucheng.taketoday.constructordi")
public class Config {

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

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

这里我们使用@Configuration注解来通知Spring,在运行时这个类提供了bean definition(@Bean注解), 并且需要对包cn.tuyucheng.taketoday.constructordi中额外的bean执行上下文扫描。

接下来,我们定义一个Car类:


@Component
public class Car {

    private final Engine engine;

    private final Transmission transmission;

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

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

通过调用Config类的@Bean注解方法,我们可以获得Engine和Transmission的实例。 最后,我们需要使用我们的POJO配置来启动ApplicationContext:

public class SpringRunner {

    public static void main(String[] args) {
        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配置文件:


<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">

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

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

    <bean id="transmission" class="cn.tuyucheng.taketoday.constructordi.domain.Transmission">
        <constructor-arg value="sliding"/>
    </bean>
</beans>

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

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

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

import cn.tuyucheng.taketoday.constructordi.domain.Car;

public class SpringRunner {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("constructordi.xml");
        Car car = context.getBean(Car.class);
    }
}

5. 利弊

与字段注入相比,构造函数注入有一些优点。

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

public class UserService {

    @Autowired
    private UserRepository userRepository;
}

在UserService实例的构建过程中,我们无法初始化userRepository状态。 实现这一点的唯一方法是通过反射API,这完全打破了封装。此外,与简单的构造函数调用相比,生成的代码更不安全。

此外,使用字段注入,我们无法强制执行类级别的不变量,因此可能会在没有正确初始化userRepository的情况下拥有UserService实例, 从而导致随机的NullPointerExceptions。相反,使用构造函数注入,更容易构建不可变组件。

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

另一方面,构造注入的主要缺点是冗长,尤其是bean具有大量依赖项时。 但有时这可能是因祸得福,因为如果出现这种情况往往意味着我我们的代码能够进一步的重构。

6. 总结

这篇简短的文章演示了通过Spring使用基于构造函数依赖注入的两种不同方法实现。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章