1. Обзор
В этом руководстве по Spring Framework мы покажем, как использовать аннотации, связанные с внедрением зависимостей, а именно аннотации @Resource
, @Inject
и @Autowired
. Эти аннотации предоставляют классам декларативный способ разрешения зависимостей:
@Autowired
ArbitraryClass arbObject;
В отличие от их прямого создания (императивный способ):
ArbitraryClass arbObject = new ArbitraryClass();
Две из трех аннотаций относятся к пакету расширения Java: javax.annotation.Resource
и javax.inject.Inject
. Аннотация @Autowired
принадлежит пакету org.springframework.beans.factory.annotation
.
Каждая из этих аннотаций может разрешать зависимости либо путем внедрения поля, либо путем внедрения установщика. Мы будем использовать упрощенный, но практичный пример, чтобы продемонстрировать различие между тремя аннотациями на основе путей выполнения каждой аннотации.
В примерах основное внимание будет уделено тому, как использовать три аннотации внедрения во время интеграционного тестирования. Требуемая тестом зависимость может быть либо произвольным файлом, либо произвольным классом.
2. `` Аннотация @Resource
_ ****
Аннотация @Resource является частью коллекции аннотаций
JSR-250 и поставляется вместе с Jakarta EE. Эта аннотация имеет следующие пути выполнения, перечисленные в порядке приоритета:
- Совпадение по имени
- Соответствие по типу
- Матч по квалификации
Эти пути выполнения применимы как к установщику, так и к внедрению поля.
2.1. Полевая инъекция
Мы можем разрешать зависимости путем внедрения поля, аннотируя переменную экземпляра аннотацией @Resource
.
2.1.1. Совпадение по имени
Мы будем использовать следующий интеграционный тест, чтобы продемонстрировать внедрение полей с совпадением по имени:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestResourceNameType.class)
public class FieldResourceInjectionIntegrationTest {
@Resource(name="namedFile")
private File defaultFile;
@Test
public void givenResourceAnnotation_WhenOnField_ThenDependencyValid(){
assertNotNull(defaultFile);
assertEquals("namedFile.txt", defaultFile.getName());
}
}
Давайте пройдемся по коду. В интеграционном тесте FieldResourceInjectionTest
в строке 7 мы разрешили зависимость по имени, передав имя компонента в качестве значения атрибута в аннотацию @Resource
:
@Resource(name="namedFile")
private File defaultFile;
Эта конфигурация разрешает зависимости, используя путь выполнения сопоставления по имени. Мы должны определить компонент namedFile
в контексте приложения ApplicationContextTestResourceNameType .
Обратите внимание, что идентификатор компонента и соответствующее значение атрибута ссылки должны совпадать:
@Configuration
public class ApplicationContextTestResourceNameType {
@Bean(name="namedFile")
public File namedFile() {
File namedFile = new File("namedFile.txt");
return namedFile;
}
}
Если мы не сможем определить компонент в контексте приложения, это приведет к возникновению исключения org.springframework.beans.factory.NoSuchBeanDefinitionException
. Мы можем продемонстрировать это, изменив значение атрибута, переданное в аннотацию @Bean в контексте приложения
ApplicationContextTestResourceNameType
, или изменив значение атрибута, переданное в аннотацию @Resource в интеграционном
тесте FieldResourceInjectionTest .
2.1.2. Соответствие по типу
Чтобы продемонстрировать путь выполнения сопоставления по типу, мы просто удаляем значение атрибута в строке 7 интеграционного теста FieldResourceInjectionTest
:
@Resource
private File defaultFile;
Затем снова запускаем тест.
Тест по-прежнему будет пройден, потому что, если аннотация @Resource
не получит имя компонента в качестве значения атрибута, Spring Framework перейдет к следующему уровню приоритета, сопоставлению по типу, чтобы попытаться разрешить зависимость.
2.1.3. Матч по квалификации
Чтобы продемонстрировать путь выполнения сопоставления по квалификатору, сценарий интеграционного тестирования будет изменен таким образом, чтобы в контексте приложения ApplicationContextTestResourceQualifier были определены два bean-компонента:
@Configuration
public class ApplicationContextTestResourceQualifier {
@Bean(name="defaultFile")
public File defaultFile() {
File defaultFile = new File("defaultFile.txt");
return defaultFile;
}
@Bean(name="namedFile")
public File namedFile() {
File namedFile = new File("namedFile.txt");
return namedFile;
}
}
Мы будем использовать интеграционный тест QualifierResourceInjectionTest
, чтобы продемонстрировать разрешение зависимости на основе квалификатора. В этом сценарии в каждую ссылочную переменную необходимо внедрить конкретную зависимость bean-компонента:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestResourceQualifier.class)
public class QualifierResourceInjectionIntegrationTest {
@Resource
private File dependency1;
@Resource
private File dependency2;
@Test
public void givenResourceAnnotation_WhenField_ThenDependency1Valid(){
assertNotNull(dependency1);
assertEquals("defaultFile.txt", dependency1.getName());
}
@Test
public void givenResourceQualifier_WhenField_ThenDependency2Valid(){
assertNotNull(dependency2);
assertEquals("namedFile.txt", dependency2.getName());
}
}
Когда мы запускаем интеграционный тест, будет выдано исключение org.springframework.beans.factory.NoUniqueBeanDefinitionException
. Это произойдет, потому что контекст приложения найдет два определения bean-компонента типа File
и не будет знать, какой bean-компонент должен разрешать зависимость.
Чтобы решить эту проблему, нам нужно обратиться к строкам с 7 по 10 интеграционного теста QualifierResourceInjectionTest :
@Resource
private File dependency1;
@Resource
private File dependency2;
Мы должны добавить следующие строки кода:
@Qualifier("defaultFile")
@Qualifier("namedFile")
Чтобы кодовый блок выглядел следующим образом:
@Resource
@Qualifier("defaultFile")
private File dependency1;
@Resource
@Qualifier("namedFile")
private File dependency2;
Когда мы снова запустим интеграционный тест, он должен пройти. Наш тест демонстрирует, что даже если мы определяем несколько bean-компонентов в контексте приложения, мы можем использовать аннотацию @Qualifier
, чтобы устранить любую путаницу, позволяя нам вводить определенные зависимости в класс.
2.2. Инъекция сеттера
Пути выполнения, выбранные при внедрении зависимостей в поле, также применимы к внедрению на основе установщика.
2.2.1. Совпадение по имени
Единственное отличие состоит в том , что интеграционный тест MethodResourceInjectionTest имеет метод установки:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestResourceNameType.class)
public class MethodResourceInjectionIntegrationTest {
private File defaultFile;
@Resource(name="namedFile")
protected void setDefaultFile(File defaultFile) {
this.defaultFile = defaultFile;
}
@Test
public void givenResourceAnnotation_WhenSetter_ThenDependencyValid(){
assertNotNull(defaultFile);
assertEquals("namedFile.txt", defaultFile.getName());
}
}
Мы разрешаем зависимости путем внедрения установщика, аннотируя соответствующий метод установщика ссылочной переменной. Затем мы передаем имя зависимости bean-компонента в качестве значения атрибута в аннотацию @Resource
:
private File defaultFile;
@Resource(name="namedFile")
protected void setDefaultFile(File defaultFile) {
this.defaultFile = defaultFile;
}
В этом примере мы будем повторно использовать зависимость bean-компонента namedFile
. Имя компонента и соответствующее значение атрибута должны совпадать.
Когда мы запустим интеграционный тест, он пройдет.
Чтобы мы могли убедиться, что путь выполнения сопоставления по имени разрешил зависимость, нам нужно изменить значение атрибута, переданное в аннотацию @Resource
, на значение по нашему выбору и снова запустить тест. На этот раз тест завершится с ошибкой NoSuchBeanDefinitionException
.
2.2.2. Соответствие по типу
Чтобы продемонстрировать выполнение на основе сеттера и сопоставления по типу, мы будем использовать интеграционный тест MethodByTypeResourceTest
:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestResourceNameType.class)
public class MethodByTypeResourceIntegrationTest {
private File defaultFile;
@Resource
protected void setDefaultFile(File defaultFile) {
this.defaultFile = defaultFile;
}
@Test
public void givenResourceAnnotation_WhenSetter_ThenValidDependency(){
assertNotNull(defaultFile);
assertEquals("namedFile.txt", defaultFile.getName());
}
}
Когда мы запустим этот тест, он пройдет.
Чтобы мы могли убедиться, что путь выполнения сопоставления по типу разрешил зависимость File
, нам нужно изменить тип класса переменной defaultFile
на другой тип класса, например String
. Затем мы можем снова выполнить интеграционный тест MethodByTypeResourceTest , и на этот раз будет
выдано исключение NoSuchBeanDefinitionException
.
Исключение подтверждает, что сопоставление по типу действительно использовалось для разрешения зависимости File .
NoSuchBeanDefinitionException подтверждает, что имя
ссылочной переменной не обязательно должно совпадать с именем компонента. Вместо этого разрешение зависимостей зависит от типа класса компонента, соответствующего типу класса ссылочной переменной.
2.2.3. Матч по квалификации
Мы будем использовать интеграционный тест MethodByQualifierResourceTest
, чтобы продемонстрировать путь выполнения match-by-qualifier:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestResourceQualifier.class)
public class MethodByQualifierResourceIntegrationTest {
private File arbDependency;
private File anotherArbDependency;
@Test
public void givenResourceQualifier_WhenSetter_ThenValidDependencies(){
assertNotNull(arbDependency);
assertEquals("namedFile.txt", arbDependency.getName());
assertNotNull(anotherArbDependency);
assertEquals("defaultFile.txt", anotherArbDependency.getName());
}
@Resource
@Qualifier("namedFile")
public void setArbDependency(File arbDependency) {
this.arbDependency = arbDependency;
}
@Resource
@Qualifier("defaultFile")
public void setAnotherArbDependency(File anotherArbDependency) {
this.anotherArbDependency = anotherArbDependency;
}
}
Наш тест демонстрирует, что даже если мы определяем несколько реализаций bean-компонентов определенного типа в контексте приложения, мы можем использовать аннотацию @Qualifier
вместе с аннотацией @Resource
для разрешения зависимости.
Подобно внедрению зависимостей на основе полей, если мы определяем несколько bean-компонентов в контексте приложения, мы должны использовать аннотацию @Qualifier
, чтобы указать, какой bean-компонент использовать для разрешения зависимостей, иначе будет выдано исключение NoUniqueBeanDefinitionException .
3. Аннотация @Inject
Аннотация @Inject принадлежит коллекции аннотаций JSR
-330 . Эта аннотация имеет следующие пути выполнения, перечисленные в порядке приоритета:
- Соответствие по типу
- Матч по квалификации
- Совпадение по имени
Эти пути выполнения применимы как к установщику, так и к внедрению полей. Чтобы мы могли получить доступ к аннотации @Inject
, мы должны объявить библиотеку javax.inject
как зависимость Gradle или Maven.
Для Градла:
testCompile group: 'javax.inject', name: 'javax.inject', version: '1'
Для Мавена:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
3.1. Полевая инъекция
3.1.1. Соответствие по типу
Мы изменим пример интеграционного теста, чтобы использовать другой тип зависимости, а именно класс ArbitraryDependency .
Зависимость класса ArbitraryDependency
просто служит простой зависимостью и не имеет дальнейшего значения:
@Component
public class ArbitraryDependency {
private final String label = "Arbitrary Dependency";
public String toString() {
return label;
}
}
Вот рассматриваемый интеграционный тест FieldInjectTest :
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestInjectType.class)
public class FieldInjectIntegrationTest {
@Inject
private ArbitraryDependency fieldInjectDependency;
@Test
public void givenInjectAnnotation_WhenOnField_ThenValidDependency(){
assertNotNull(fieldInjectDependency);
assertEquals("Arbitrary Dependency",
fieldInjectDependency.toString());
}
}
В отличие от аннотации @Resource
, которая сначала разрешает зависимости по имени, поведение аннотации @Inject
по умолчанию заключается в разрешении зависимостей по типу.
Это означает, что даже если имя переменной ссылки на класс отличается от имени bean-компонента, зависимость все равно будет разрешена, при условии, что bean-компонент определен в контексте приложения. Обратите внимание на имя ссылочной переменной в следующем тесте:
@Inject
private ArbitraryDependency fieldInjectDependency;
отличается от имени компонента, настроенного в контексте приложения:
@Bean
public ArbitraryDependency injectDependency() {
ArbitraryDependency injectDependency = new ArbitraryDependency();
return injectDependency;
}
Когда мы выполняем тест, мы можем разрешить зависимость.
3.1.2. Матч по квалификации
Что делать, если существует несколько реализаций определенного типа класса, и для определенного класса требуется определенный компонент? Давайте изменим пример интеграционного тестирования, чтобы он требовал другой зависимости.
В этом примере мы подклассируем класс ArbitraryDependency
, используемый в примере сопоставления по типу, для создания класса AnotherArbitraryDependency
:
public class AnotherArbitraryDependency extends ArbitraryDependency {
private final String label = "Another Arbitrary Dependency";
public String toString() {
return label;
}
}
Цель каждого тестового примера — убедиться, что мы правильно внедряем каждую зависимость в каждую ссылочную переменную:
@Inject
private ArbitraryDependency defaultDependency;
@Inject
private ArbitraryDependency namedDependency;
Мы можем использовать интеграционный тест FieldQualifierInjectTest
, чтобы продемонстрировать соответствие по квалификатору:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestInjectQualifier.class)
public class FieldQualifierInjectIntegrationTest {
@Inject
private ArbitraryDependency defaultDependency;
@Inject
private ArbitraryDependency namedDependency;
@Test
public void givenInjectQualifier_WhenOnField_ThenDefaultFileValid(){
assertNotNull(defaultDependency);
assertEquals("Arbitrary Dependency",
defaultDependency.toString());
}
@Test
public void givenInjectQualifier_WhenOnField_ThenNamedFileValid(){
assertNotNull(defaultDependency);
assertEquals("Another Arbitrary Dependency",
namedDependency.toString());
}
}
Если у нас есть несколько реализаций определенного класса в контексте приложения, а интеграционный тест FieldQualifierInjectTest
пытается внедрить зависимости описанным ниже способом, будет выдано исключение NoUniqueBeanDefinitionException
:
@Inject
private ArbitraryDependency defaultDependency;
@Inject
private ArbitraryDependency namedDependency;
Генерация этого исключения — это способ Spring Framework указать, что существует несколько реализаций определенного класса, и он запутался в том, какую из них использовать. Чтобы прояснить путаницу, мы можем перейти к строкам 7 и 10 интеграционного теста FieldQualifierInjectTest
:
@Inject
private ArbitraryDependency defaultDependency;
@Inject
private ArbitraryDependency namedDependency;
Мы можем передать требуемое имя компонента в аннотацию @Qualifier
, которую мы используем вместе с аннотацией @Inject
. Вот как теперь будет выглядеть блок кода:
@Inject
@Qualifier("defaultFile")
private ArbitraryDependency defaultDependency;
@Inject
@Qualifier("namedFile")
private ArbitraryDependency namedDependency;
Аннотация @Qualifier
предполагает строгое соответствие при получении имени компонента. Мы должны убедиться, что имя компонента правильно передано квалификатору
, иначе будет сгенерировано исключение NoUniqueBeanDefinitionException
. Если мы снова запустим тест, он должен пройти.
3.1.3. Совпадение по имени
Интеграционный тест FieldByNameInjectTest
, используемый для демонстрации совпадения по имени, аналогичен пути выполнения сопоставления по типу. Единственная разница в том, что теперь нам требуется определенный компонент, а не определенный тип. В этом примере мы снова создаем подкласс класса ArbitraryDependency
, чтобы создать класс YetAnotherArbitraryDependency
:
public class YetAnotherArbitraryDependency extends ArbitraryDependency {
private final String label = "Yet Another Arbitrary Dependency";
public String toString() {
return label;
}
}
Чтобы продемонстрировать путь выполнения сопоставления по имени, мы будем использовать следующий интеграционный тест:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestInjectName.class)
public class FieldByNameInjectIntegrationTest {
@Inject
@Named("yetAnotherFieldInjectDependency")
private ArbitraryDependency yetAnotherFieldInjectDependency;
@Test
public void givenInjectQualifier_WhenSetOnField_ThenDependencyValid(){
assertNotNull(yetAnotherFieldInjectDependency);
assertEquals("Yet Another Arbitrary Dependency",
yetAnotherFieldInjectDependency.toString());
}
}
Перечислим контекст приложения:
@Configuration
public class ApplicationContextTestInjectName {
@Bean
public ArbitraryDependency yetAnotherFieldInjectDependency() {
ArbitraryDependency yetAnotherFieldInjectDependency =
new YetAnotherArbitraryDependency();
return yetAnotherFieldInjectDependency;
}
}
Если мы запустим интеграционный тест, он пройдет.
Чтобы убедиться, что мы внедрили зависимость путем сопоставления по имени, нам нужно изменить значение, letAnotherFieldInjectDependency
, которое было передано в аннотацию @Named
, на другое имя по нашему выбору. Когда мы снова запустим тест, будет выброшено исключение NoSuchBeanDefinitionException .
3.2. Инъекция сеттера
Внедрение на основе установщика для аннотации @Inject
похоже на подход, используемый для внедрения на основе установщика @Resource
. Вместо того, чтобы аннотировать ссылочную переменную, мы аннотируем соответствующий метод установки. Пути выполнения, за которыми следует внедрение зависимостей на основе поля, также применяются к внедрению на основе установщика.
4. Аннотация @Autowired
Поведение аннотации @Autowired
аналогично аннотации @Inject
. Единственное отличие состоит в том, что аннотация @Autowired
является частью среды Spring. Эта аннотация имеет те же пути выполнения, что и аннотация @Inject
, перечисленные в порядке приоритета:
- Соответствие по типу
- Матч по квалификации
- Совпадение по имени
Эти пути выполнения применимы как к установщику, так и к внедрению поля.
4.1. Полевая инъекция
4.1.1. Соответствие по типу
Пример интеграционного тестирования, используемый для демонстрации пути выполнения сопоставления по типу @Autowired
, будет аналогичен тесту, используемому для демонстрации пути выполнения сопоставления по типу @Inject .
Мы используем следующий интеграционный тест FieldAutowiredTest
, чтобы продемонстрировать соответствие по типу с помощью аннотации @Autowired
:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestAutowiredType.class)
public class FieldAutowiredIntegrationTest {
@Autowired
private ArbitraryDependency fieldDependency;
@Test
public void givenAutowired_WhenSetOnField_ThenDependencyResolved() {
assertNotNull(fieldDependency);
assertEquals("Arbitrary Dependency", fieldDependency.toString());
}
}
Перечислим контекст приложения для этого интеграционного теста:
@Configuration
public class ApplicationContextTestAutowiredType {
@Bean
public ArbitraryDependency autowiredFieldDependency() {
ArbitraryDependency autowiredFieldDependency =
new ArbitraryDependency();
return autowiredFieldDependency;
}
}
Мы используем этот интеграционный тест, чтобы продемонстрировать, что сопоставление по типу имеет приоритет над другими путями выполнения. Обратите внимание на имя эталонной переменной в строке 8 интеграционного теста FieldAutowiredTest
:
@Autowired
private ArbitraryDependency fieldDependency;
Это отличается от имени компонента в контексте приложения:
@Bean
public ArbitraryDependency autowiredFieldDependency() {
ArbitraryDependency autowiredFieldDependency =
new ArbitraryDependency();
return autowiredFieldDependency;
}
Когда мы запустим тест, он должен пройти.
Чтобы убедиться, что зависимость действительно разрешена с использованием пути выполнения match-by-type, нам нужно изменить тип ссылочной переменной fieldDependency
и снова запустить интеграционный тест. На этот раз интеграционный тест FieldAutowiredTest
завершится ошибкой с выдачей исключения NoSuchBeanDefinitionException
. Это подтверждает, что мы использовали сопоставление по типу для разрешения зависимости.
4.1.2. Матч по квалификации
Что, если мы столкнулись с ситуацией, когда мы определили несколько реализаций bean-компонентов в контексте приложения:
@Configuration
public class ApplicationContextTestAutowiredQualifier {
@Bean
public ArbitraryDependency autowiredFieldDependency() {
ArbitraryDependency autowiredFieldDependency =
new ArbitraryDependency();
return autowiredFieldDependency;
}
@Bean
public ArbitraryDependency anotherAutowiredFieldDependency() {
ArbitraryDependency anotherAutowiredFieldDependency =
new AnotherArbitraryDependency();
return anotherAutowiredFieldDependency;
}
}
Если мы выполним следующий интеграционный тест FieldQualifierAutowiredTest
, будет выдано исключение NoUniqueBeanDefinitionException :
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestAutowiredQualifier.class)
public class FieldQualifierAutowiredIntegrationTest {
@Autowired
private ArbitraryDependency fieldDependency1;
@Autowired
private ArbitraryDependency fieldDependency2;
@Test
public void givenAutowiredQualifier_WhenOnField_ThenDep1Valid(){
assertNotNull(fieldDependency1);
assertEquals("Arbitrary Dependency", fieldDependency1.toString());
}
@Test
public void givenAutowiredQualifier_WhenOnField_ThenDep2Valid(){
assertNotNull(fieldDependency2);
assertEquals("Another Arbitrary Dependency",
fieldDependency2.toString());
}
}
Исключение связано с неоднозначностью, вызванной двумя bean-компонентами, определенными в контексте приложения. Spring Framework не знает, какая зависимость bean-компонента должна быть автоматически привязана к какой ссылочной переменной. Мы можем решить эту проблему, добавив аннотацию @Qualifier
к строкам 7 и 10 интеграционного теста FieldQualifierAutowiredTest
:
@Autowired
private FieldDependency fieldDependency1;
@Autowired
private FieldDependency fieldDependency2;
чтобы блок кода выглядел следующим образом:
@Autowired
@Qualifier("autowiredFieldDependency")
private FieldDependency fieldDependency1;
@Autowired
@Qualifier("anotherAutowiredFieldDependency")
private FieldDependency fieldDependency2;
Когда мы снова запустим тест, он будет пройден.
4.1.3. Совпадение по имени
Мы будем использовать тот же сценарий интеграционного теста, чтобы продемонстрировать путь выполнения сопоставления по имени, используя аннотацию @Autowired
для внедрения зависимости от поля. При автосвязывании зависимостей по имени аннотацию @ComponentScan
необходимо использовать с контекстом приложения ApplicationContextTestAutowiredName
:
@Configuration
@ComponentScan(basePackages={"com.foreach.dependency"})
public class ApplicationContextTestAutowiredName {
}
Мы используем аннотацию @ComponentScan
для поиска в пакетах классов Java, которые были аннотированы аннотацией @Component
. Например, в контексте приложения пакет com.foreach.dependency
будет просканирован на наличие классов, аннотированных аннотацией @Component
. В этом сценарии Spring Framework должен обнаружить класс ArbitraryDependency , который имеет аннотацию
@Component
:
@Component(value="autowiredFieldDependency")
public class ArbitraryDependency {
private final String label = "Arbitrary Dependency";
public String toString() {
return label;
}
}
Значение атрибута autowiredFieldDependency
, переданное в аннотацию @Component
, сообщает Spring Framework, что класс ArbitraryDependency
является компонентом с именем autowiredFieldDependency
. Чтобы аннотация @Autowired разрешала
зависимости по имени, имя компонента должно соответствовать имени поля, определенному в интеграционном тесте FieldAutowiredNameTest
; обратитесь к строке 8:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestAutowiredName.class)
public class FieldAutowiredNameIntegrationTest {
@Autowired
private ArbitraryDependency autowiredFieldDependency;
@Test
public void givenAutowiredAnnotation_WhenOnField_ThenDepValid(){
assertNotNull(autowiredFieldDependency);
assertEquals("Arbitrary Dependency",
autowiredFieldDependency.toString());
}
}
Когда мы запустим интеграционный тест FieldAutowiredNameTest
, он пройдет.
Но откуда мы знаем, что аннотация @Autowired
действительно вызвала путь выполнения сопоставления по имени? Мы можем изменить имя ссылочной переменной autowiredFieldDependency
на другое имя по нашему выбору, а затем снова запустить тест.
На этот раз тест завершится ошибкой и будет выброшено исключение NoUniqueBeanDefinitionException
. Аналогичной проверкой может быть изменение значения атрибута @Component
autowiredFieldDependency
на другое значение по нашему выбору и повторный запуск теста. Также будет сгенерировано исключение NoUniqueBeanDefinitionException .
Это исключение является доказательством того, что если мы используем неправильное имя компонента, не будет найден ни один допустимый компонент. Вот как мы узнаем, что путь выполнения сопоставления по имени был вызван.
4.2. Инъекция сеттера
Setter-based injection for the @Autowired
annotation is similar to the approach demonstrated for the @Resource
setter-based injection. Instead of annotating the reference variable with the @Inject
annotation, we annotate the corresponding setter. The execution paths followed by field-based dependency injection also apply to setter-based injection.
5. Applying These Annotations
This raises the question of which annotation should be used and under what circumstances. The answer to these questions depends on the design scenario faced by the application in question, and how the developer wishes to leverage polymorphism based on the default execution paths of each annotation.
5.1. Application-Wide Use of Singletons Through Polymorphism
If the design is such that application behaviors are based on implementations of an interface or an abstract class, and these behaviors are used throughout the application, then we can use either the @Inject
or @Autowired
annotation.
The benefit of this approach is that when we upgrade the application, or apply a patch in order to fix a bug, classes can be swapped out with minimal negative impact to the overall application behavior. In this scenario, the primary default execution path is match-by-type.
5.2. Fine-Grained Application Behavior Configuration Through Polymorphism
If the design is such that the application has complex behavior, each behavior is based on different interfaces/abstract classes, and the usage of each of these implementations varies across the application, then we can use the @Resource
annotation. In this scenario, the primary default execution path is match-by-name.
5.3. Dependency Injection Should Be Handled Solely by the Jakarta EE Platform
If there is a design mandate for all dependencies to be injected by the Jakarta EE Platform as opposed to Spring, then the choice is between the @Resource
annotation and the @Inject
annotation. We should narrow down the final decision between the two annotations based on which default execution path is required.
5.4. Dependency Injection Should Be Handled Solely by the Spring Framework
If the mandate is for all dependencies to be handled by the Spring Framework, the only choice is the @Autowired
annotation.
5.5. Discussion Summary
The table below summarizes our discussion.
| Scenario | @Resource | @Inject | @Autowired |
| Application-wide use of singletons through polymorphism | ✗ | ✔ | ✔ |
| Fine-grained application behavior configuration through polymorphism | ✔ | ✗ | ✗ |
| Dependency injection should be handled solely by the Jakarta EE platform | ✔ | ✔ | ✗ |
| Dependency injection should be handled solely by the Spring Framework | ✗ | ✗ | ✔ |
6. Conclusion
In this article, we aimed to provide a deeper insight into the behavior of each annotation. Understanding how each annotation behaves will contribute to better overall application design and maintenance.
Код, использованный в ходе обсуждения, можно найти на GitHub .