Qu'est-ce que Field Injection et comment l'éviter?

130

J'ai lu dans certains articles sur Spring MVC et les portlets que l' injection de champ n'est pas recommandée. Si je comprends bien, l' injection de champ se produit lorsque vous injectez un Bean avec @Autowiredcomme ceci:

@Component
public class MyComponent {
    @Autowired
    private Cart cart;
}

Au cours de mes recherches, j'ai également lu sur l' injection de constructeur :

@Component
public class MyComponent {
    private final Cart cart;

    @Autowired
    public MyComponent(Cart cart){
       this.cart = cart;
    }
}

Quels sont les avantages et les inconvénients de ces deux types d'injections?


EDIT 1: Comme cette question est marquée comme un double de cette question, je l'ai vérifiée. Parce qu'il n'y a pas d'exemples de code ni dans la question ni dans les réponses, je ne sais pas si j'ai raison de deviner quel type d'injection j'utilise.

T. Jung
la source
3
Si l'injection de champ est aussi mauvaise que vous le décrivez, pourquoi Spring le permet-elle? L'injection de champ a ses propres avantages, en rendant le code plus lisible et moins verbeux. Si vous êtes suffisamment discipliné dans votre codage, vous pouvez être sûr que les choses ne se casseraient pas même si vous utilisez l'injection de champ.
cendres le
@ashes Parce que c'était une fonctionnalité intéressante à l'époque et que les implications n'étaient pas entièrement réfléchies. La même raison qui Date(int,int,int)existe.
chrylis -cautouslyoptimistic-

Réponses:

225

Types d'injection

Il existe trois options pour la manière dont les dépendances peuvent être injectées dans un bean:

  1. Par un constructeur
  2. Par des setters ou d'autres méthodes
  3. Par la réflexion, directement dans les champs

Vous utilisez l'option 3. C'est ce qui se passe lorsque vous utilisez @Autowireddirectement sur votre terrain.


Directives d'injection

Une directive générale, qui est recommandée par Spring (voir les sections sur la DI basée sur le constructeur ou la DI basée sur le Setter ) est la suivante:

  • Pour les dépendances obligatoires ou lorsque vous visez l'immuabilité, utilisez l'injection de constructeur
  • Pour les dépendances facultatives ou modifiables, utilisez l'injection de setter
  • Évitez l'injection sur le terrain dans la plupart des cas

Inconvénients de l'injection sur le terrain

Les raisons pour lesquelles l'injection de champ est mal vue sont les suivantes:

  • Vous ne pouvez pas créer d'objets immuables, comme vous pouvez le faire avec l'injection de constructeur
  • Vos classes ont un couplage étroit avec votre conteneur DI et ne peuvent pas être utilisées en dehors de celui-ci
  • Vos classes ne peuvent pas être instanciées (par exemple dans les tests unitaires) sans réflexion. Vous avez besoin du conteneur DI pour les instancier, ce qui fait que vos tests ressemblent davantage à des tests d'intégration
  • Vos dépendances réelles sont cachées de l'extérieur et ne sont pas reflétées dans votre interface (que ce soit les constructeurs ou les méthodes)
  • Il est vraiment facile d'avoir dix dépendances. Si vous utilisiez l'injection de constructeur, vous auriez un constructeur avec dix arguments, ce qui indiquerait que quelque chose est louche. Mais vous pouvez ajouter des champs injectés en utilisant l'injection de champ indéfiniment. Avoir trop de dépendances est un signal d'alarme indiquant que la classe fait généralement plus d'une chose et peut violer le principe de responsabilité unique.

Conclusion

Selon vos besoins, vous devez principalement utiliser l'injection de constructeur ou un mélange d'injection de constructeur et de setter. L'injection de champ présente de nombreux inconvénients et doit être évitée. Le seul avantage de l'injection de champ est qu'elle est plus pratique à écrire, ce qui ne l'emporte pas sur tous les inconvénients.


Lectures complémentaires

J'ai écrit un article de blog sur les raisons pour lesquelles l'injection de champ n'est généralement pas recommandée: Injection de dépendance de champ considérée comme nuisible .

Vojtech Ruzicka
la source
12
Ce n'est généralement pas une bonne idée et pas agréable de dire au monde "l'injection de champ doit être évitée". Montrez les avantages et les contras et laissez les autres décider eux-mêmes;) Beaucoup de gens ont d'autres expériences et leur propre façon de voir les choses.
dieter
6
C'est peut-être le cas ici, mais il y a d'autres cas où la communauté est parvenue à un consensus général pour décourager quelque chose. Prenez, par exemple, la notation hongroise.
Jannik
Vous donnez quelques bons points comme la testabilité et la visibilité des dépendances mais je ne suis pas d'accord avec tous. L'injection de constructeur n'a pas d'inconvénients? Avoir 5 ou 6 champs à injecter en classe qui effectue de vraies compositions d'appel peut être souhaitable. Je suis également en désaccord avec vous avec l'immuabilité. Avoir des champs finaux n'est pas obligatoire pour avoir une classe immuable. Il est préférable. Ce qui est très différent.
davidxxx
Je pense que vous vouliez dire "Pour les dépendances obligatoires ou lorsque vous visez l' immuabilité "
Alex Terreaux
1
Je faisais référence au lien au début de la réponse qui renvoie aux documents du printemps
Vojtech Ruzicka
47

Il s'agit de l'une des discussions sans fin dans le développement de logiciels, mais les principaux influenceurs de l'industrie sont de plus en plus avisés sur le sujet et ont commencé à suggérer l'injection de constructeur comme meilleure option.

Injection de constructeur

Avantages:

  • Meilleure testabilité . Vous n'avez besoin d'aucune bibliothèque moqueuse ou d'un contexte Spring dans les tests unitaires. Vous pouvez créer un objet que vous souhaitez tester avec le nouveau mot-clé. De tels tests sont toujours plus rapides car ils ne reposent pas sur le mécanisme de réflexion. ( Cette question a été posée 30 minutes plus tard. Si l'auteur avait utilisé l'injection de constructeur, elle ne serait pas apparue).
  • Immuabilité . Une fois les dépendances définies, elles ne peuvent pas être modifiées.
  • Code plus sûr . Après l'exécution d'un constructeur, votre objet est prêt à être utilisé car vous pouvez valider tout ce qui a été passé en paramètre. L'objet peut être prêt ou non, il n'y a pas d'état entre les deux. Avec l'injection de champ, vous introduisez une étape intermédiaire lorsque l'objet est fragile.
  • Expression plus propre des dépendances obligatoires . L'injection de champ est ambiguë à cet égard.
  • Fait réfléchir les développeurs à la conception . dit a écrit sur un constructeur avec 8 paramètres, ce qui est en fait le signe d'une mauvaise conception et de l'anti-modèle de l'objet Dieu . Peu importe qu'une classe ait 8 dépendances dans son constructeur ou dans des champs, c'est toujours faux. Les gens sont plus réticents à ajouter plus de dépendances à un constructeur que via des champs. Cela fonctionne comme un signal à votre cerveau que vous devez vous arrêter un moment et réfléchir à la structure de votre code.

Les inconvénients:

  • Plus de code (mais les IDE modernes atténuent la douleur).

Fondamentalement, l'injection de champ est le contraire.

Daniel Olszewski
la source
1
testabilité, oui, c'était un cauchemar pour moi de me moquer des haricots injectés sur le terrain. Une fois, j'ai utilisé l'injection de contsructor, je n'ai pas besoin de faire de moquerie inutile
kenobiwan
25

Question de goût. C'est votre décision.

Mais je peux expliquer pourquoi je n'utilise jamais l' injection de constructeur .

  1. Je ne veux pas mettre en œuvre un constructeur pour tous mes @Service, @Repositoryet les @Controllerharicots. Je veux dire, il y a environ 40 à 50 haricots ou plus. Chaque fois que j'ajoute un nouveau champ, je devrais étendre le constructeur. Non, je n'en veux pas et je n'ai pas à le faire.

  2. Et si votre Bean (Service ou Controller) nécessite l'injection de beaucoup d'autres beans? Un constructeur avec plus de 4 paramètres est très moche.

  3. Si j'utilise CDI, le constructeur ne me concerne pas.


EDIT # 1 : Vojtech Ruzicka a déclaré:

la classe a trop de dépendances et viole probablement le principe de responsabilité unique et devrait être refactorisée

Oui. Théorie et réalité. Voici un exemple: DashboardControllermappé sur un seul chemin *:8080/dashboard.

My DashboardControllerrecueille beaucoup d'informations d'autres services pour les afficher dans un tableau de bord / une page de présentation du système. J'ai besoin de ce contrôleur unique. Je ne dois donc sécuriser que ce chemin (filtre d'authentification de base ou de rôle d'utilisateur).

EDIT # 2 : Puisque tout le monde se concentre sur les 8 paramètres du constructeur ... C'était un exemple réel - un code hérité des clients. J'ai changé ça. La même argumentation s'applique à moi pour 4+ paramètres.

Tout est question d'injection de code, pas de construction d'instances.

dieter
la source
34
Un constructeur très laid avec 8 dépendances est en fait génial car c'est un drapeau rouge que quelque chose ne va pas, la classe a trop de dépendances et viole probablement le principe de responsabilité unique et devrait être refactorisée. C'est en fait une bonne chose.
Vojtech Ruzicka
6
@VojtechRuzicka ce n'est pas bien sûr mais parfois vous ne pouvez pas l'éviter.
dieter
4
Je dirais qu'une règle empirique de 3 dépendances, sans parler de 40-50, pour n'importe quelle classe devrait être un signe que vous devez refactoriser. Il n'y a aucun moyen pour une classe avec 40 dépendances de s'en tenir au principal de responsabilité unique ou au principal d'ouverture / fermeture.
Amin J
4
@AminJ La règle est géniale, mais la réalité est différente. L'entreprise dans laquelle je travaille depuis plus de 20 ans et nous avons beaucoup de code hérité. Le refactoring est une bonne idée, mais cela coûte de l'argent. Aussi, je ne sais pas pourquoi je le dis mais je ne voulais pas dire 40-50 dépendances, je veux dire 40-50 beans, composants, modules ...
dieter
7
@dit, votre situation est clairement celle dans laquelle la dette technique vous pousse à faire des choix sous-optimaux. Selon vos propres mots, vous vous trouvez dans une situation où votre prise de décision est considérablement influencée par le code hérité de plus de 20 ans. Lorsque vous débutez dans un nouveau projet, recommanderiez-vous toujours l'injection de champ, plutôt que l'injection de constructeur? Vous devriez peut-être mettre une mise en garde dans votre réponse pour indiquer dans quels cas vous choisiriez l'injection sur le terrain.
Umar Farooq Khawaja
0

Un autre commentaire - Vojtech Ruzicka a déclaré que Spring injectait des haricots de trois manières (la réponse avec le plus grand nombre de points):

  1. Par un constructeur
  2. Par des setters ou d'autres méthodes
  3. Par la réflexion, directement dans les champs

Cette réponse est fausse - parce que POUR CHAQUE TYPE D'INJECTION, LE RESSORT UTILISE LA RÉFLEXION! Utilisez IDE, définissez le point d'arrêt sur le poseur / constructeur et vérifiez.

Cela peut être une question de goût mais cela peut aussi être une question de CAS. @dieter a fourni un excellent cas où l'injection sur le terrain est meilleure. Si vous utilisez l'injection de champ dans les tests d'intégration qui configurent le contexte Spring - l'argument avec la testabilité de la classe est également invalide - à moins que vous ne souhaitiez écrire plus tard des tests dans vos tests d'intégration;)

wujek.oczko
la source