Câblage au printemps: @Autowired, @Resource et @Inject

Câblage au printemps: @Autowired, @Resource et @Inject

1. Vue d'ensemble

Cet article de Spring Framework démontrera l'utilisation des annotations liées à l'injection de dépendances, à savoir les annotations@Resource,@Inject et@Autowired. Ces annotations fournissent aux classes un moyen déclaratif de résoudre les dépendances. Par exemple:

@Autowired
ArbitraryClass arbObject;

au lieu de les instancier directement (de manière impérative), par exemple:

ArbitraryClass arbObject = new ArbitraryClass();

Deux des trois annotations appartiennent au package d'extension Java:javax.annotation.Resource etjavax.inject.Inject. L'annotation@Autowired appartient au packageorg.springframework.beans.factory.annotation.

Chacune de ces annotations peut résoudre les dépendances soit par injection de champ, soit par injection de setter. Un exemple simplifié, mais pratique, sera utilisé pour démontrer la distinction entre les trois annotations, en fonction des chemins d’exécution pris par chaque annotation.

Les exemples porteront sur l'utilisation des trois annotations d'injection lors des tests d'intégration. La dépendance requise par le test peut être un fichier arbitraire ou une classe arbitraire.

2. L'annotation@Resource

L'annotation@Resource fait partie de la collection d'annotationsJSR-250 et est fournie avec Java EE. Cette annotation comporte les chemins d’exécution suivants, classés par ordre de priorité:

  1. Match par nom

  2. Match par type

  3. Match par qualificatif

Ces chemins d’exécution s’appliquent à la fois au setter et à l’injection de champ.

2.1. Injection de champ

La résolution des dépendances par injection de champ est obtenue en annotant une variable d'instance avec l'annotation@Resource.

2.1.1. Match par nom

Le test d'intégration utilisé pour démontrer l'injection de champ apparié par nom est répertorié comme suit:

@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());
    }
}

Passons en revue le code. Dans le test d'intégration deFieldResourceInjectionTest, à la ligne 7, la résolution de la dépendance par nom est obtenue en passant le nom du bean comme valeur d'attribut à l'annotation@Resource:

@Resource(name="namedFile")
private File defaultFile;

Cette configuration résoudra les dépendances à l'aide du chemin d'exécution correspondant à chaque nom. Le beannamedFile doit être défini dans le contexte d'applicationApplicationContextTestResourceNameType.

Notez que l'id du bean et la valeur de l'attribut de référence correspondant doivent correspondre:

@Configuration
public class ApplicationContextTestResourceNameType {

    @Bean(name="namedFile")
    public File namedFile() {
        File namedFile = new File("namedFile.txt");
        return namedFile;
    }
}

Si vous ne définissez pas le bean dans le contexte de l'application, unorg.springframework.beans.factory.NoSuchBeanDefinitionException sera renvoyé. Ceci peut être démontré en modifiant la valeur d'attribut passée dans l'annotation@Bean, dans le contexte d'applicationApplicationContextTestResourceNameType; ou la modification de la valeur d'attribut passée dans l'annotation@Resource, dans le test d'intégration deFieldResourceInjectionTest.

2.1.2. Match par type

Pour illustrer le chemin d'exécution de correspondance par type, supprimez simplement la valeur d'attribut à la ligne 7 du test d'intégration deFieldResourceInjectionTest pour qu'elle ressemble à ceci:

@Resource
private File defaultFile;

et relancez le test.

Le test passera toujours car si l'annotation@Resource ne reçoit pas de nom de bean comme valeur d'attribut, Spring Framework procédera au niveau de priorité suivant, correspondance par type, afin d'essayer de résoudre la dépendance .

2.1.3. Match par qualificatif

Pour illustrer le chemin d'exécution de correspondance par qualificatif, le scénario de test d'intégration sera modifié afin qu'il y ait deux beans définis dans le contexte d'applicationApplicationContextTestResourceQualifier:

@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;
    }
}

Le test d'intégration deQualifierResourceInjectionTest sera utilisé pour démontrer la résolution des dépendances correspondance par qualificatif. Dans ce scénario, une dépendance de bean spécifique doit être injectée dans chaque variable de référence:

@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());
    }
}

Exécutez le test d'intégration et unorg.springframework.beans.factory.NoUniqueBeanDefinitionException est renvoyé. Cette exception est levée car le contexte d'application a trouvé deux définitions de bean de typeFile, et il ne sait pas quel bean doit résoudre la dépendance.

Pour résoudre ce problème, reportez-vous aux lignes 7 à 10 du test d'intégration deQualifierResourceInjectionTest:

@Resource
private File dependency1;

@Resource
private File dependency2;

et ajoutez les lignes de code suivantes:

@Qualifier("defaultFile")

@Qualifier("namedFile")

de sorte que le bloc de code se présente comme suit:

@Resource
@Qualifier("defaultFile")
private File dependency1;

@Resource
@Qualifier("namedFile")
private File dependency2;

Exécutez le test d'intégration à nouveau, cette fois, il devrait réussir. L'objectif de ce test était de démontrer que même s'il y a plusieurs beans définis dans un contexte d'application, l'annotation@Qualifier élimine toute confusion en permettant l'injection de dépendances spécifiques dans une classe.

2.2. Injection de poseur

Les chemins d’exécution pris lors de l’injection de dépendances sur un champ s’appliquent à l’injection basée sur le setter.

2.2.1. Match par nom

La seule différence est que le test d'intégrationMethodResourceInjectionTest a une méthode setter:

@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());
    }
}

La résolution des dépendances par injection de setter se fait en annotant la méthode setter correspondante d'une variable de référence. Passez le nom de la dépendance de bean comme valeur d'attribut à l'annotation@Resource:

private File defaultFile;

@Resource(name="namedFile")
protected void setDefaultFile(File defaultFile) {
    this.defaultFile = defaultFile;
}

La dépendance du beannamedFile sera réutilisée dans cet exemple. Le nom du bean et la valeur de l'attribut correspondant doivent correspondre.

Exécutez le test d'intégration tel quel et il passera.

Pour voir que la dépendance a bien été résolue par le chemin d'exécution de correspondance par nom, modifiez la valeur d'attribut transmise à l'annotation@Resource par une valeur de votre choix et relancez le test. Cette fois, le test échouera avec unNoSuchBeanDefinitionException.

2.2.2. Match par type

Pour démontrer l'exécution de correspondance par type basée sur un setter, nous utiliserons le test d'intégration deMethodByTypeResourceTest:

@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());
    }
}

Exécutez ce test tel quel, et il passera.

Afin de vérifier que la dépendanceFile a bien été résolue par le chemin d'exécution de correspondance par type, changez le type de classe de la variabledefaultFile en un autre type de classe commeString. Exécutez à nouveau le test d'intégration deMethodByTypeResourceTest et cette fois, unNoSuchBeanDefinitionException sera lancé.

L'exception vérifie que la correspondance par type a bien été utilisée pour résoudre la dépendanceFile. LeNoSuchBeanDefinitionException confirme que le nom de la variable de référence n'a pas besoin de correspondre au nom du bean. Au lieu de cela, la résolution des dépendances dépend du type de classe du bean correspondant au type de classe de la variable de référence.

2.2.3. Match par qualificatif

Le test d'intégration deMethodByQualifierResourceTest sera utilisé pour démontrer le chemin d'exécution de correspondance par qualificatif:

@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;
    }
}

L'objectif de ce test est de démontrer que même si plusieurs implémentations de bean d'un type particulier sont définies dans un contexte d'application, une annotation@Qualifier peut être utilisée avec l'annotation@Resource pour résoudre une dépendance.

Similaire à l'injection de dépendances basée sur un champ, s'il y a plusieurs beans définis dans un contexte d'application, unNoUniqueBeanDefinitionException est renvoyé si aucune annotation@Qualifier n'est utilisée pour spécifier quel bean doit être utilisé pour résoudre les dépendances.

3. L'annotation@Inject

L'annotation@Inject appartient à la collection d'annotationsJSR-330. Cette annotation comporte les chemins d’exécution suivants, classés par ordre de priorité:

  1. Match par type

  2. Match par qualificatif

  3. Match par nom

Ces chemins d’exécution s’appliquent à la fois au setter et à l’injection de champ. Pour accéder à l'annotation@Inject, la bibliothèquejavax.inject doit être déclarée en tant que dépendance Gradle ou Maven.

Pour Gradle:

testCompile group: 'javax.inject', name: 'javax.inject', version: '1'

Pour Maven:


    javax.inject
    javax.inject
    1

3.1. Injection de champ

3.1.1. Match par type

L'exemple de test d'intégration sera modifié pour utiliser un autre type de dépendance, à savoir la classeArbitraryDependency. La dépendance de classeArbitraryDependency sert simplement de dépendance simple et n'a aucune autre signification. Il est répertorié comme suit:

@Component
public class ArbitraryDependency {

    private final String label = "Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

Le test d'intégration deFieldInjectTest en question est répertorié comme suit:

@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());
    }
}

Contrairement à l'annotation@Resource, qui résout d'abord les dépendances par leur nom; le comportement par défaut de l'annotation@Inject résout les dépendances par type.

Cela signifie que même si un nom de variable de référence de classe diffère du nom du bean, la dépendance sera toujours résolue, à condition que le bean soit défini dans le contexte de l'application. Notez le nom de la variable de référence dans le test suivant:

@Inject
private ArbitraryDependency fieldInjectDependency;

diffère du nom du bean configuré dans le contexte de l'application:

@Bean
public ArbitraryDependency injectDependency() {
    ArbitraryDependency injectDependency = new ArbitraryDependency();
    return injectDependency;
}

et lorsque le test est exécuté, il est capable de résoudre la dépendance.

3.1.2. Match par qualificatif

Mais que se passe-t-il s'il existe plusieurs implémentations d'un type de classe particulier et qu'une classe donnée nécessite un bean spécifique? Laissez-nous modifier l'exemple de test d'intégration afin qu'une autre dépendance soit requise.

Dans cet exemple, nous sous-classons la classeArbitraryDependency, utilisée dans l'exemple de correspondance par type, pour créer la classeAnotherArbitraryDependency:

public class AnotherArbitraryDependency extends ArbitraryDependency {

    private final String label = "Another Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

L'objectif de chaque cas de test est de s'assurer que chaque dépendance est correctement injectée dans chaque variable de référence:

@Inject
private ArbitraryDependency defaultDependency;

@Inject
private ArbitraryDependency namedDependency;

Le test d'intégration deFieldQualifierInjectTest utilisé pour démontrer la correspondance par qualificatif est répertorié comme suit:

@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());
    }
}

S'il existe plusieurs implémentations d'une classe particulière dans un contexte d'application et que le test d'intégration deFieldQualifierInjectTest tente d'injecter les dépendances de la manière indiquée ci-dessous:

@Inject
private ArbitraryDependency defaultDependency;

@Inject
private ArbitraryDependency namedDependency;

unNoUniqueBeanDefinitionException sera lancé.

Lancer cette exception est la façon dont Spring Framework indique qu’il existe plusieurs implémentations d’une certaine classe et qu’on ne sait pas laquelle utiliser. Afin d'élucider la confusion, passez aux lignes 7 et 10 du test d'intégrationFieldQualifierInjectTest:

@Inject
private ArbitraryDependency defaultDependency;

@Inject
private ArbitraryDependency namedDependency;

transmettez le nom du bean requis à l'annotation@Qualifier, qui est utilisée avec l'annotation@Inject. Le bloc de code va maintenant ressembler à ceci:

@Inject
@Qualifier("defaultFile")
private ArbitraryDependency defaultDependency;

@Inject
@Qualifier("namedFile")
private ArbitraryDependency namedDependency;

L'annotation@Qualifier attend une correspondance stricte lors de la réception d'un nom de bean. Assurez-vous que le nom du bean est correctement passé auxQualifier, sinon, unNoUniqueBeanDefinitionException sera renvoyé. Relancez le test et cette fois, il devrait passer.

3.1.3. Match par nom

Le test d'intégration deFieldByNameInjectTest utilisé pour démontrer la correspondance par nom est similaire au chemin d'exécution de correspondance par type. La seule différence est maintenant qu'un haricot spécifique est requis, par opposition à un type spécifique. Dans cet exemple, nous sous-classons à nouveau la classeArbitraryDependency pour produire la classeYetAnotherArbitraryDependency:

public class YetAnotherArbitraryDependency extends ArbitraryDependency {

    private final String label = "Yet Another Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

Afin de démontrer le chemin d'exécution par nom, nous allons utiliser le test d'intégration suivant:

@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());
    }
}

Le contexte de l'application est répertorié comme suit:

@Configuration
public class ApplicationContextTestInjectName {

    @Bean
    public ArbitraryDependency yetAnotherFieldInjectDependency() {
        ArbitraryDependency yetAnotherFieldInjectDependency =
          new YetAnotherArbitraryDependency();
        return yetAnotherFieldInjectDependency;
    }
}

Exécutez le test d'intégration tel quel, et il passera.

Afin de vérifier que la dépendance a bien été injectée par le chemin d'exécution de correspondance par nom, changez la valeur,yetAnotherFieldInjectDependency, qui a été transmise à l'annotation@Named par un autre nom de votre choix. Exécutez à nouveau le test - cette fois, unNoSuchBeanDefinitionException est lancé.

3.2. Injection de poseur

L'injection basée sur le poseur pour l'annotation@Inject est similaire à l'approche utilisée pour l'injection basée sur le poseur@Resource. Au lieu d'annoter la variable de référence, la méthode de définition correspondante est annotée. Les chemins d'exécution suivis par l'injection de dépendance basée sur le champ s'appliquent également à l'injection basée sur le setter.

4. L'annotation@Autowired

Le comportement de l'annotation@Autowired est similaire à l'annotation@Inject. La seule différence est que l'annotation@Autowired fait partie du framework Spring. Cette annotation a les mêmes chemins d'exécution que l'annotation@Inject, classés par ordre de priorité:

  1. Match par type

  2. Match par qualificatif

  3. Match par nom

Ces chemins d’exécution s’appliquent à la fois au setter et à l’injection de champ.

4.1. Injection de champ

4.1.1. Match par type

L'exemple de test d'intégration utilisé pour démontrer le chemin d'exécution de correspondance par type de@Autowired sera similaire au test utilisé pour démontrer le chemin d'exécution de correspondance par type de@Inject. Le test d'intégrationFieldAutowiredTest utilisé pour démontrer la correspondance par type à l'aide de l'annotation@Autowired est répertorié comme suit:

@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());
    }
}

Le contexte d'application de ce test d'intégration est répertorié comme suit:

@Configuration
public class ApplicationContextTestAutowiredType {

    @Bean
    public ArbitraryDependency autowiredFieldDependency() {
        ArbitraryDependency autowiredFieldDependency =
          new ArbitraryDependency();
        return autowiredFieldDependency;
    }
}

L’objectif du test d’intégration est de démontrer que la correspondance par type est prioritaire sur les autres chemins d’exécution. Remarquez à la ligne 8 du test d'intégrationFieldAutowiredTest comment le nom de la variable de référence:

@Autowired
private ArbitraryDependency fieldDependency;

est différent du nom du bean dans le contexte de l'application:

@Bean
public ArbitraryDependency autowiredFieldDependency() {
    ArbitraryDependency autowiredFieldDependency =
      new ArbitraryDependency();
    return autowiredFieldDependency;
}

Lorsque le test est exécuté, il passera.

Afin de confirmer que la dépendance a bien été résolue en utilisant le chemin d'exécution de correspondance par type, changez le type de la variable de référencefieldDependency et relancez le test d'intégration. Cette fois-ci, le test d'intégration deFieldAutowiredTest doit échouer, avec unNoSuchBeanDefinitionException émis. Ceci vérifie que le type de correspondance a été utilisé pour résoudre la dépendance.

4.1.2. Match par qualificatif

Et si nous faisions face à une situation où plusieurs implémentations de beans ont été définies dans le contexte d'application, comme celle répertoriée ci-dessous:

@Configuration
public class ApplicationContextTestAutowiredQualifier {

    @Bean
    public ArbitraryDependency autowiredFieldDependency() {
        ArbitraryDependency autowiredFieldDependency =
          new ArbitraryDependency();
        return autowiredFieldDependency;
    }

    @Bean
    public ArbitraryDependency anotherAutowiredFieldDependency() {
        ArbitraryDependency anotherAutowiredFieldDependency =
          new AnotherArbitraryDependency();
        return anotherAutowiredFieldDependency;
    }
}

Si le test d'intégration deFieldQualifierAutowiredTest, répertorié ci-dessous, est exécuté:

@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());
    }
}

unNoUniqueBeanDefinitionException sera lancé.

L'exception est due à l'ambiguïté causée par les deux beans définis dans le contexte de l'application. Le framework Spring ne sait pas quelle dépendance de bean doit être liée automatiquement à quelle variable de référence. Résolvez ce problème en ajoutant l'annotation@Qualifier aux lignes 7 et 10 du test d'intégrationFieldQualifierAutowiredTest:

@Autowired
private FieldDependency fieldDependency1;

@Autowired
private FieldDependency fieldDependency2;

de sorte que le bloc de code se présente comme suit:

@Autowired
@Qualifier("autowiredFieldDependency")
private FieldDependency fieldDependency1;

@Autowired
@Qualifier("anotherAutowiredFieldDependency")
private FieldDependency fieldDependency2;

Exécutez le test à nouveau et cette fois, il passera.

4.1.3. Match par nom

Le même scénario de test d'intégration sera utilisé pour démontrer le chemin d'exécution de correspondance par nom lors de l'utilisation de l'annotation@Autowired pour injecter une dépendance de champ. Lors du câblage automatique des dépendances par nom, l'annotation@ComponentScan doit être utilisée avec le contexte d'application,ApplicationContextTestAutowiredName:

@Configuration
@ComponentScan(basePackages={"com.example.dependency"})
    public class ApplicationContextTestAutowiredName {
}

L'annotation@ComponentScan recherchera dans les packages les classes Java qui ont été annotées avec l'annotation@Component. Par exemple, dans le contexte de l'application, le packagecom.example.dependency sera scanné pour les classes qui ont été annotées avec l'annotation@Component. Dans ce scénario, Spring Framework doit détecter la classeArbitraryDependency, qui a l'annotation@Component:

@Component(value="autowiredFieldDependency")
public class ArbitraryDependency {

    private final String label = "Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

La valeur d'attribut,autowiredFieldDependency, transmise dans l'annotation@Component, indique au Spring Framework que la classeArbitraryDependency est un composant nomméautowiredFieldDependency. Pour que l'annotation@Autowired résout les dépendances par nom, le nom du composant doit correspondre au nom du champ défini dans le test d'intégration deFieldAutowiredNameTest; veuillez vous référer à la ligne 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());
    }
}

Lorsque le test d'intégration deFieldAutowiredNameTest est exécuté tel quel, il réussit.

Mais comment savons-nous que l'annotation@Autowired a vraiment invoqué le chemin d'exécution de correspondance par nom? Remplacez le nom de la variable de référenceautowiredFieldDependency par un autre nom de votre choix, puis relancez le test.

Cette fois, le test échouera et unNoUniqueBeanDefinitionException est lancé. Une vérification similaire consisterait à changer la valeur d'attribut@Component,autowiredFieldDependency, par une autre valeur de votre choix et à relancer le test. UnNoUniqueBeanDefinitionException sera également lancé.

Cette exception est la preuve que si un nom de bean incorrect est utilisé, aucun bean valide ne sera trouvé. Par conséquent, le chemin d'exécution correspondant à chaque nom a été appelé.

4.2. Injection de poseur

L'injection basée sur le poseur pour l'annotation@Autowired est similaire à l'approche démontrée pour l'injection basée sur le poseur@Resource. Au lieu d'annoter la variable de référence avec l'annotation@Inject, le setter correspondant est annoté. Les chemins d'exécution suivis par l'injection de dépendance basée sur le champ s'appliquent également à l'injection basée sur le setter.

5. Application de ces annotations

Cela soulève la question de savoir quelle annotation doit être utilisée et dans quelles circonstances. La réponse à ces questions dépend du scénario de conception rencontré par l'application en question et de la manière dont le développeur souhaite exploiter le polymorphisme en fonction des chemins d'exécution par défaut de chaque annotation.

5.1. Utilisation à l'échelle de l'application des singletons par polymorphisme

Si la conception est telle que les comportements de l'application sont basés sur les implémentations d'une interface ou d'une classe abstraite, et que ces comportements sont utilisés dans toute l'application, utilisez l'annotation@Inject ou@Autowired.

L'avantage de cette approche est que lorsque l'application est mise à niveau, un correctif doit être appliqué pour corriger un bogue. les classes peuvent alors être permutées avec un impact négatif minimal sur le comportement général de l'application. Dans ce scénario, le chemin d'exécution principal par défaut est la correspondance par type.

5.2. Configuration fine du comportement des applications grâce au polymorphisme

Si la conception est telle que l'application a un comportement complexe, chaque comportement est basé sur différentes interfaces / classes abstraites, et l'utilisation de chacune de ces implémentations varie à travers l'application, alors utilisez l'annotation@Resource. Dans ce scénario, le chemin d'exécution principal par défaut est la correspondance par nom.

5.3. L'injection de dépendances doit être gérée uniquement par la plate-forme Java EE

S'il existe un mandat de conception pour toutes les dépendances à injecter par la plate-forme Java EE et non par Spring, alors le choix est entre l'annotation@Resource et l'annotation@Inject. Vous devez affiner la décision finale entre les deux annotations en fonction du chemin d’exécution par défaut requis.

5.4. L'injection de dépendances doit être gérée uniquement par le framework Spring

Si le mandat est que toutes les dépendances soient gérées par Spring Framework, le seul choix est l'annotation@Autowired.

5.5. Résumé de la discussion

Le tableau ci-dessous résume la discussion.

Scénario

@Ressource

@Injecter

@Autowired

Utilisation à l'échelle de l'application des singletons par polymorphisme

Configuration fine du comportement des applications grâce au polymorphisme

L'injection de dépendances doit être gérée uniquement par la plate-forme Java EE

L'injection de dépendances doit être gérée uniquement par Spring Framework

6. Conclusion

L'article visait à fournir un aperçu plus approfondi du comportement de chaque annotation. Comprendre le comportement de chaque annotation contribuera à améliorer la conception et la maintenance globales des applications.

Le code utilisé lors de la discussion se trouve surGitHub.