Introduction à l’inversion du contrôle et injection de dépendance avec ressort

Introduction à l'inversion du contrôle et injection de dépendance avec ressort

1. Vue d'ensemble

Dans cet article, nous présenterons les concepts d'IoC (Inversion of Control) et DI (Dependency Injection), puis nous examinerons comment ils sont mis en œuvre dans le framework Spring.

Lectures complémentaires:

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

Cet article comparera et opposera l'utilisation des annotations liées à l'injection de dépendance, à savoir les annotations @Resource, @Inject et @Autowired.

Read more

@Component vs @Repository et @Service au printemps

Découvrez les différences entre les annotations @Component, @Repository et @Service et le moment de les utiliser.

Read more

2. Qu'est-ce que l'inversion de contrôle?

L'inversion de contrôle est un principe de génie logiciel selon lequel le contrôle d'objets ou de parties d'un programme est transféré dans un conteneur ou une structure. Il est le plus souvent utilisé dans le contexte de la programmation orientée objet.

Contrairement à la programmation traditionnelle, dans laquelle notre code personnalisé appelle la bibliothèque, IoC permet à un framework de prendre le contrôle du flux d'un programme et d'appeler notre code personnalisé. Pour cela, les frameworks utilisent des abstractions avec un comportement supplémentaire intégré. If we want to add our own behavior, we need to extend the classes of the framework or plugin our own classes.

Les avantages de cette architecture sont:

  • découpler l'exécution d'une tâche de sa mise en œuvre

  • facilitant le basculement entre différentes implémentations

  • plus grande modularité d'un programme

  • une plus grande facilité à tester un programme en isolant un composant ou en se moquant de ses dépendances et en permettant aux composants de communiquer via des contrats

L'inversion de contrôle peut être réalisée à l'aide de divers mécanismes tels que: modèle de conception de stratégie, modèle de localisation de service, modèle d'usine et injection de dépendance (DI).

Nous allons examiner DI ensuite.

3. Qu'est-ce que l'injection de dépendance?

L'injection de dépendances est un modèle par lequel implémenter l'IoC, où le contrôle inversé est le paramètre des dépendances de l'objet.

Le fait de connecter des objets à d’autres objets, ou d’injecter des objets dans d’autres objets, est effectué par un assembleur plutôt que par les objets eux-mêmes.

Voici comment créer une dépendance d'objet dans la programmation traditionnelle:

public class Store {
    private Item item;

    public Store() {
        item = new ItemImpl1();
    }
}

Dans l'exemple ci-dessus, nous devons instancier une implémentation de l'interfaceItem dans la classeStore elle-même.

En utilisant DI, nous pouvons réécrire l'exemple sans spécifier l'implémentation deItem que nous voulons:

public class Store {
    private Item item;
    public Store(Item item) {
        this.item = item;
    }
}

Dans les sections suivantes, nous verrons comment nous pouvons fournir la mise en œuvre deItem via des métadonnées.

L'IoC et la DI sont des concepts simples, mais qui ont des implications profondes dans la façon dont nous structurons nos systèmes, ils valent donc la peine d'être bien compris.

4. Le conteneur Spring IoC

Un conteneur IoC est une caractéristique commune des frameworks qui implémentent IoC.

Dans le framework Spring, le conteneur IoC est représenté par l'interfaceApplicationContext. Le conteneur Spring est responsable de l'instanciation, de la configuration et de l'assemblage des objets appelésbeans, ainsi que de la gestion de leur cycle de vie.

Le framework Spring fournit plusieurs implémentations de l'interfaceApplicationContext -ClassPathXmlApplicationContext etFileSystemXmlApplicationContext pour les applications autonomes, etWebApplicationContext pour les applications Web.

Afin d'assembler les beans, le conteneur utilise des métadonnées de configuration, qui peuvent être sous forme de configuration XML ou d'annotations.

Voici une façon d'instancier manuellement un conteneur:

ApplicationContext context
  = new ClassPathXmlApplicationContext("applicationContext.xml");

Pour définir l'attributitem dans l'exemple ci-dessus, nous pouvons utiliser des métadonnées. Ensuite, le conteneur lira ces métadonnées et les utilisera pour assembler les beans lors de l'exécution.

L'injection de dépendances dans Spring peut être effectuée via des constructeurs, des setters ou des champs.

5. Injection de dépendances basée sur le constructeur

Dans le cas deconstructor-based dependency injection, le conteneur invoquera un constructeur avec des arguments représentant chacun une dépendance que nous voulons définir.

Spring résout chaque argument principalement par type, suivi du nom de l'attribut et de l'index pour la désambiguïsation. Voyons la configuration d’un bean et de ses dépendances à l’aide des annotations:

@Configuration
public class AppConfig {

    @Bean
    public Item item1() {
        return new ItemImpl1();
    }

    @Bean
    public Store store() {
        return new Store(item1());
    }
}

L'annotation@Configuration indique que la classe est une source de définitions de bean. Nous pouvons également l'ajouter à plusieurs classes de configuration.

L'annotation@Bean est utilisée sur une méthode pour définir un bean. Si nous ne spécifions pas de nom personnalisé, le nom du bean sera par défaut le nom de la méthode.

Pour un bean avec la portée par défautsingleton, Spring vérifie d'abord si une instance mise en cache du bean existe déjà et n'en crée une nouvelle que si ce n'est pas le cas. Si nous utilisons la portéeprototype, le conteneur renvoie une nouvelle instance de bean pour chaque appel de méthode.

Une autre façon de créer la configuration des beans consiste à utiliser la configuration XML:



    

6. Injection de dépendances basée sur un poseur

Pour le DI basé sur le setter, le conteneur appellera les méthodes de setter de notre classe après avoir appelé un constructeur sans argument ou une méthode de fabrique statique sans argument pour instancier le bean. Créons cette configuration à l'aide d'annotations:

@Bean
public Store store() {
    Store store = new Store();
    store.setItem(item1());
    return store;
}

Nous pouvons également utiliser XML pour la même configuration de beans:


    

Les types d'injection basés sur le constructeur et ceux basés sur le fixateur peuvent être combinés pour le même grain. La documentation de Spring recommande d'utiliser l'injection basée sur le constructeur pour les dépendances obligatoires et l'injection basée sur le paramètre pour les optionnelles.

7. Basé sur le terrain Injection de dépendance

Dans le cas de Field-Based DI, nous pouvons injecter les dépendances en les marquant avec une annotation@Autowired:

public class Store {
    @Autowired
    private Item item;
}

Lors de la construction de l'objetStore, s'il n'y a pas de constructeur ou de méthode de définition pour injecter le beanItem, le conteneur utilisera la réflexion pour injecterItem dansStore.

Nous pouvons également y parvenir en utilisantXML configuration.

Cette approche peut paraître plus simple et plus nette, mais son utilisation n’est pas recommandée car elle présente quelques inconvénients, tels que:

  • Cette méthode utilise la réflexion pour injecter les dépendances, ce qui est plus coûteux que l’injection basée sur le constructeur ou sur le setter.

  • Il est très facile de continuer à ajouter plusieurs dépendances en utilisant cette approche. Si vous utilisiez l'injection de constructeur avec plusieurs arguments, nous aurions pu penser que la classe fait plus que ce qui peut violer le principe de responsabilité unique.

Plus d'informations sur l'annotation@Autowired peuvent être trouvées dans l'articleWiring In Spring.

8. Dépendances de câblage automatique

Wiring permet au conteneur Spring de résoudre automatiquement les dépendances entre les beans collaborant en inspectant les beans qui ont été définis.

Il existe quatre modes de câblage automatique d'un bean à l'aide d'une configuration XML:

  • no: la valeur par défaut - cela signifie qu'aucun autowiring n'est utilisé pour le bean et que nous devons nommer explicitement les dépendances

  • Le câblage automatique debyName: est effectué en fonction du nom de la propriété, donc Spring recherchera un bean avec le même nom que la propriété qui doit être définie

  • byType: similaire au câblage automatique debyName, uniquement en fonction du type de propriété. Cela signifie que Spring recherchera un haricot avec le même type de propriété à définir. S'il y a plus d'un bean de ce type, le framework lève une exception.

  • Le câblage automatique deconstructor: est effectué en fonction des arguments du constructeur, ce qui signifie que Spring cherchera des beans avec le même type que les arguments du constructeur

Par exemple, passons automatiquement le beanitem1 défini ci-dessus par type dans le beanstore:

@Bean(autowire = Autowire.BY_TYPE)
public class Store {

    private Item item;

    public setItem(Item item){
        this.item = item;
    }
}

Nous pouvons également injecter des beans en utilisant l'annotation@Autowired pour l'autowiring par type:

public class Store {

    @Autowired
    private Item item;
}

S'il y a plus d'un bean du même type, nous pouvons utiliser l'annotation@Qualifier pour référencer un bean par son nom:

public class Store {

    @Autowired
    @Qualifier("item1")
    private Item item;
}

Maintenant, passons automatiquement les beans par type via la configuration XML:

 

Ensuite, injectons un bean nomméitem dans la propriétéitem du beanstore par son nom via XML:




Nous pouvons également remplacer le câblage automatique en définissant explicitement les dépendances à l'aide d'arguments ou de paramètres de constructeur.

9. Beans initialisés paresseux

Par défaut, le conteneur crée et configure tous les beans singleton lors de l'initialisation. Pour éviter cela, vous pouvez utiliser l'attributlazy-init avec la valeurtrue sur la configuration du bean:

Par conséquent, le beanitem1 ne sera initialisé que lors de sa première demande, et non au démarrage. L'avantage de cela est le temps d'initialisation plus rapide, mais le compromis est que les erreurs de configuration ne peuvent être découvertes qu'après la demande du bean, ce qui peut prendre plusieurs heures ou même plusieurs jours après le démarrage de l'application.

10. Conclusion

Dans cet article, nous avons présenté les concepts d'inversion de contrôle et d'injection de dépendances et les avons illustrés dans le framework Spring.

Vous pouvez en savoir plus sur ces concepts dans les articles de Martin Fowler:

Et vous pouvez en savoir plus sur les implémentations Spring d'IoC et DI dans lesSpring Framework Reference Documentation.