SpringBoot中,@Autowired和构造器注入是两种常用的依赖注入方式。不过,在集成测试环境下,会出现只能使用@Autowired,而构造器注入却无法正常工作的情况,这背后的原因是什么呢?

一、Spring测试上下文的初始化机制导致的问题

在Spring的测试框架中,像使用@SpringBootTest@ContextConfiguration注解进行测试时,Spring会借助反射机制来创建测试类的实例。默认情况下,Spring倾向于使用无参构造器去实例化测试类。

(一)构造器注入的示例及问题

假设我们有如下测试类:

@SpringBootTest public class MyIntegrationTest { // 声明一个需要注入的服务 private final MyService myService; // 通过构造器进行注入 public MyIntegrationTest(MyService myService) { this.myService = myService; } @Test public void testSomething() { // 测试逻辑 } } 

Spring的测试框架在创建MyIntegrationTest实例时,会尝试调用无参构造器。但这个测试类中并没有无参构造器,只有带参数的构造器用于构造器注入,这就导致实例化过程失败,进而使得依赖注入无法完成。

(二)使用@Autowired的解决方法

@SpringBootTest public class MyIntegrationTest { // 使用@Autowired注解进行字段注入 @Autowired private MyService myService; @Test public void testSomething() { // 测试逻辑 } } 

使用@Autowired时,Spring先通过无参构造器实例化测试类,之后再利用反射机制,将依赖注入到被@Autowired标注的字段中。这种方式不依赖特定的构造器,所以能正常实现依赖注入。

二、构造器注入在测试类中的限制

构造器注入在生产代码中应用广泛,它能确保对象创建时依赖就已注入,有效避免依赖未初始化的问题。然而在测试类场景下,构造器注入存在一些局限性。

  • Spring无法自动调用带参数的构造器:Spring的测试框架默认采用无参构造器来实例化测试类。若要使用带参数的构造器,就必须进行手动配置,比如借助@TestInstance注解或者自定义测试上下文来实现。
  • 测试类的生命周期与依赖注入的冲突:测试类的实例化和依赖注入是两个独立的流程。构造器注入要求在实例化时就提供依赖,可Spring的测试框架通常是在实例化完成后才进行依赖注入操作。

三、@Autowired能正常工作的原因

(一)字段注入的工作原理

当使用@Autowired进行字段注入时,Spring首先通过无参构造器实例化测试类。接着,利用反射技术,将依赖注入到被@Autowired标记的字段中。

(二)适合测试场景的原因

在测试类中,字段注入展现出了更高的灵活性,因为它不依赖特定的构造器。而且测试类一般对依赖管理的要求不像生产代码那么严格,所以字段注入在测试场景中更为常见。

四、在测试中使用构造器注入的方法

如果在测试类中确实需要使用构造器注入,可以尝试以下几种方法:

(一)使用@TestInstance注解

默认情况下,JUnit在每次运行测试方法时,都会创建一个全新的测试类实例。而使用@TestInstance(TestInstance.Lifecycle.PER_CLASS)注解后,JUnit会使用单个测试类实例,这样就能支持构造器注入了。

@SpringBootTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MyIntegrationTest { // 声明需要注入的服务 private final MyService myService; // 构造器注入 public MyIntegrationTest(MyService myService) { this.myService = myService; } @Test public void testSomething() { // 测试逻辑 } } 

(二)使用@BeforeAll和手动注入

要是无法使用@TestInstance注解,还可以借助@BeforeAll注解手动初始化依赖。

@SpringBootTest public class MyIntegrationTest { // 声明需要注入的服务 private MyService myService; // 尝试使用构造器注入,并结合@Autowired @Autowired public MyIntegrationTest(MyService myService) { this.myService = myService; } @BeforeAll public static void setUp() { // 手动初始化依赖的相关代码 } @Test public void testSomething() { // 测试逻辑 } } 

(三)自定义测试上下文

还可以通过自定义Spring的测试上下文,对测试类的实例化方式进行配置,从而实现构造器注入。不过这种方式相对复杂,需要对Spring的测试框架有更深入的理解。

五、总结

在Spring Boot测试中,之所以通常只能使用@Autowired进行依赖注入,是因为Spring测试框架默认使用无参构造器实例化测试类,而构造器注入需要带参数的构造器,二者存在冲突。而@Autowired采用反射进行字段注入,不依赖构造器,所以能正常工作。

如果确实要在测试中使用构造器注入,可以选择@TestInstance(TestInstance.Lifecycle.PER_CLASS)注解,或者采用手动注入等方式来自定义测试类的实例化流程。不过,从简便性和与Spring测试框架默认行为的兼容性考虑,在测试类中优先推荐使用@Autowired进行字段注入。