Spring @Autowired поведение отличается в тестах от компонентов

правила/поведение вокруг @Autowired разные при написании тестов? Кажется, что с помощью теста вы можете autowire к конкретному типу, но если вы попробуете то же самое внутри @Component это не удастся. Это надуманный пример, но это то, с чем я столкнулся, и я просто пытаюсь лучше понять.

надуманный пример кода:

public interface Gizmo {

  void whirr();
}

@Configuration
public class GizmoConfiguration {

  @Bean
  @Profile("no-dependencies")
  public Gizmo fooGizmoBean() {
    return new FooGizmo();
  }

  @Bean
  @Profile("!no-dependencies")
  public Gizmo barGizmoBean() {
    return new BarGizmo();
  }

  public class FooGizmo implements Gizmo {
    @Override
    public void whirr() {
    }
  }

  public class BarGizmo implements Gizmo {
    @Override
    public void whirr() {
    }
  }
}

тест, который работает нормально:

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles(Application.Profiles.NO_DEPENDENCIES)
public class TestClass {

  @Autowired
  private GizmoConfiguration.FooGizmo gizmo;

  @Test
  public void test() {
    assertNotNull(gizmo);
  }
}

компонент, который вызывает java.lang.IllegalStateException: Failed to load ApplicationContext:

@Component
public class TestComponent {

  @Autowired
  private GizmoConfiguration.FooGizmo gizmo;
}

потому что из:

No qualifying bean of type 'GizmoConfiguration$FooGizmo' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

1 ответов


правила / поведение вокруг @Autowired отличаются при написании тесты?

не совсем: правила на самом деле точно такие же. Разница заключается в сроках относительно того, как Spring определяет, является ли данный Боб кандидатом autowire.

кажется, что с помощью теста вы можете autowire к конкретному типу, но если вы попробуете то же самое внутри @Component, он потерпит неудачу.

I поймите, почему вы так думаете, поскольку ваш пример демонстрирует такое поведение, но ваш анализ не совсем корректен.

Итак, позвольте мне объяснить...

Когда весна пытается выполнить autowiring для вашего @Component class, единственная информация, которую он имеет о типах (т. е. классах и интерфейсах) для бобов, поступающих из @Bean методы-это информация, доступная в @Bean формальная подпись метода.

в вашем примере, когда весна ищет так называемые "кандидаты autowire" для инъекции в ваш @Component, Spring видит только Боб типа Gizmo для fooGizmoBean() @Bean метод. Вот почему вы видите ошибку "нет квалификационного компонента типа" GizmoConfiguration$FooGizmo"", которая оказывается полностью правильной.

если вы хотите весной, чтобы иметь возможность autowire ваш @Component используя конкретный тип, вам придется переопределить подпись вашего fooGizmoBean() @Bean метод, чтобы возвратить FooGizmo вместо Gizmo.

Итак, это первая половина истории. Вторая половина истории заключается в том, почему Spring Testcontext Framework может выполнять автозапуск по конкретному типу для тестового экземпляра.

причина, которая работает в том, что ApplicationContext уже полностью запущен (т. е. все зерна были созданы и все @Bean методы были вызваны контейнером) к тому времени, когда платформа тестирования пытается выполнить инъекцию зависимостей. Тем самым момент времени fooGizmoBean() метод уже был вызван Spring, и Spring теперь знает, что конкретный тип на самом деле является FooGizmo. Таким образом, @Autowired FooGizmo gizmo; работает в тесте.