1. 概述
现代软件设计中最重要的开发原则之一就是依赖注入(Dependency Injection, DI),它自然而然地源于另一个关键原则:模块化。
本教程将探讨Spring框架中的一种特定的DI技术——构造函数依赖注入。简单来说,这意味着我们在实例化类时传递所需的组件。
首先,我们需要在pom.xml
中导入spring-boot-starter-web
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
接下来,我们需要设置一个配置文件。这个文件可以是POJO(Plain Old Java Object)或XML文件,根据个人喜好选择。
2. 注解配置
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");
}
}
这里我们使用注解通知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会在包扫描过程中遇到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.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>
注意,constructor-arg
可以接受一个字面值或另一个bean的引用,而且可以提供可选的明确的index
和type
。我们可以使用Type
和index
属性来解决歧义(例如,如果构造函数接受多个相同类型的参数)。
name
属性也可以用于XML到Java变量的匹配,但这时你的代码必须使用调试标志编译。
在这种情况下,我们需要使用ClassPathXmlApplicationContext
来启动我们的Spring应用程序上下文:
ApplicationContext context = new ClassPathXmlApplicationContext("baeldung.xml");
Car car = context.getBean(Car.class);
5. 优缺点
与字段注入相比,构造函数注入有一些优势。
首先,它是可测试性的。 假设我们要单元测试一个使用字段注入的Spring bean:
public class UserService {
@Autowired
private UserRepository userRepository;
}
在构建UserService
实例时,我们无法初始化userRepository
的状态。唯一的方法是通过反射API,这完全破坏了封装性。此外,与简单的构造函数调用相比,结果代码的安全性会降低。
此外,在字段注入中,我们不能强制类级不变量。 因此,可能会有一个没有正确初始化的userRepository
的UserService
实例。因此,我们可能会在这里和那里遇到随机的NullPointerException
。另一方面,使用构造函数创建对象实例在面向对象的角度来看更为自然。
另外,使用构造函数创建对象实例更符合面向对象的原则。
然而,构造函数注入的主要缺点是其冗长性,特别是当bean有很多依赖项时。有时,这可能是一个伪装的祝福,因为我们可能会更加努力地保持依赖项数量最小化。
6. 总结
本文简要展示了使用Spring框架实现两种不同的构造函数依赖注入的基本方法。
本文的完整实现可以在GitHub上找到。