237 votes

Qu'est-ce que l'injection de champ exactement et comment l'éviter?

J'ai lu dans certains posts sur Spring MVC et Portlets que l'injection de champs n'est pas recommandée. Comme je le comprends, l'injection de champs est lorsque vous injectez un Bean avec @Autowired comme ceci :

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

Pendant 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 doublon de cette question, je l'ai vérifiée. Comme il n'y a pas d'exemples de code ni dans la question ni dans les réponses, il n'est pas clair pour moi si ma supposition sur le type d'injection que j'utilise est correcte.

3 votes

Si l'injection de champ est aussi mauvaise que vous le décrivez, pourquoi Spring la permet-elle? L'injection de champ a ses propres avantages, en rendant le code plus lisible et moins verbeux. Si vous êtes assez discipliné dans votre codage, vous pouvez être sûr que les choses ne se casseront pas même si vous utilisez l'injection de champ.

2 votes

@ashes Parce que c'était une fonctionnalité intéressante à l'époque, et les implications n'avaient pas été entièrement réfléchies. La même raison pour laquelle Date(int, int, int) existe.

372voto

Vojtech Ruzicka Points 8142

Types d'injection

Il existe trois options pour injecter des dépendances dans un bean :

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

Vous utilisez l'option 3. C'est ce qui se passe lorsque vous utilisez @Autowired directement sur votre champ.


Directives d'injection

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

  • Pour les dépendances obligatoires ou lorsque vous visez l'immutabilité, utilisez l'injection par constructeur
  • Pour les dépendances optionnelles ou changeables, utilisez l'injection par setter
  • Évitez l'injection de champ dans la plupart des cas

Inconvénients de l'injection de champ

Les raisons pour lesquelles l'injection de champ est déconseillée sont les suivantes :

  • Vous ne pouvez pas créer d'objets immuables, comme vous le pouvez avec l'injection par constructeur
  • Vos classes sont fortement couplées avec votre conteneur d'injection de dépendances et ne peuvent pas être utilisées en dehors de celui-ci
  • Vos classes ne peuvent pas être instanciées (par exemple dans des tests unitaires) sans réflexion. Vous avez besoin du conteneur d'injection de dépendances pour les instancier, ce qui rend vos tests plus comme des tests d'intégration
  • Vos dépendances réelles sont cachées à 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 une dizaine de dépendances. Si vous utilisiez l'injection par constructeur, vous auriez un constructeur avec dix arguments, ce qui signalerait quelque chose de 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 qu'elle peut violer le Principe de Responsabilité Unique.

Conclusion

En fonction de vos besoins, vous devriez principalement utiliser l'injection par constructeur ou un mélange d'injection par constructeur et par setter. L'injection de champ a de nombreux inconvénients et devrait être évitée. Le seul avantage de l'injection de champ est qu'elle est plus pratique à écrire, ce qui ne compense pas tous les inconvénients.


Lecture complémentaire

J'ai écrit un article de blog sur pourquoi l'injection de champ n'est généralement pas recommandée : L'injection de dépendance par champ considérée comme nocive.

37 votes

Il est généralement une mauvaise idée et pas très agréable de dire au monde "l'injection de champ doit être évitée". Montrez les avantages et inconvénients et laissez les autres décider par eux-mêmes ;) Beaucoup de gens ont d'autres expériences et leur propre façon de voir les choses.

14 votes

Cela 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.

2 votes

Vous donnez certains bons points tels que la testabilité et la visibilité des dépendances, mais je ne suis pas d'accord sur tout. L'injection de constructeur n'a aucun inconvénient? Avoir 5 ou 6 champs à injecter dans une classe qui effectue de vraies compositions d'appels peut être souhaitable. Je ne suis pas d'accord non plus avec vous sur l'immutabilité. Avoir des champs finaux n'est pas obligatoire pour avoir une classe immuable. C'est préférable. Ce qui est très différent.

73voto

Daniel Olszewski Points 8626

Ceci est l'une des discussions sans fin dans le développement de logiciels, mais les principaux influenceurs de l'industrie deviennent plus catégoriques sur le sujet et ont commencé à suggérer l'injection de constructeur comme meilleure option.

Injection de constructeur

Avantages:

  • Meilleure testabilité. Vous n'avez pas besoin de bibliothèque de simulation ou d'un contexte Spring dans les tests unitaires. Vous pouvez créer un objet que vous souhaitez tester avec le mot-clé new. 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 n'aurait pas apparu).
  • Immutilité. 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 tant que paramètre. L'objet peut être prêt ou non, il n'y a pas d'état intermédiaire. 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ë à ce sujet.
  • Amène les développeurs à réfléchir au design. a écrit à propos d'un constructeur avec 8 paramètres, ce qui est en réalité le signe d'une mauvaise conception et du l'anti-pattern de l'objet-Dieu. Peu importe si une classe a 8 dépendances dans son constructeur ou dans des champs, c'est toujours incorrect. Les gens sont plus réticents à ajouter plus de dépendances à un constructeur qu'avec des champs. Cela fonctionne comme un signal pour votre cerveau que vous devriez vous arrêter un instant et réfléchir à la structure de votre code.

Inconvénients:

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

En fin de compte, l'injection de champ est l'inverse.

1 votes

Testabilité, oui, C'était un cauchemar pour moi de simuler les beans injectés sur le terrain. Une fois, j'ai utilisé l'injection de constructeur, je n'avais pas besoin de faire de simulations inutiles

42voto

dit Points 5514

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

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

  1. Je ne veux pas implémenter un constructeur pour tous mes @Service, @Repository et @Controller beans. Je veux dire, il y a environ 40-50 beans ou plus. Chaque fois que j'ajoute un nouveau champ, je devrais étendre le constructeur. Non. Je ne le veux pas et je ne suis pas obligé de le faire.

  2. Et si votre Bean (Service ou Controller) nécessite beaucoup d'autres beans à injecter ? un constructeur avec 4+ paramètres est très moche.

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


MODIFICATION #1: Vojtech Ruzicka a dit:

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

Oui. Théorie et réalité. Voici un exemple: DashboardController mappé à un seul chemin *:8080/dashboard.

Mon DashboardController collecte beaucoup d'informations d'autres services pour les afficher dans un tableau de bord / vue d'ensemble du système. J'ai besoin de ce contrôleur unique. Donc je dois sécuriser uniquement ce chemin (authentification de base ou filtre de rôle d'utilisateur).

MODIFICATION #2: Puisque tout le monde est focalisé sur les 8 paramètres dans le constructeur... C'était un exemple concret - du code hérité des clients. J'ai changé cela. La même argumentation s'applique pour moi pour 4+ paramètres.

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

46 votes

Un constructeur très laid avec 8 dépendances est en fait impressionnant car c'est un signal d'alarme 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. En fait, c'est une bonne chose.

9 votes

@VojtechRuzicka ce n'est certainement pas agréable, mais parfois vous ne pouvez pas l'éviter.

4 votes

Je dirais qu'une règle de base de 3, sans parler de 40-50, dépendances pour une classe devrait être un signe que vous devez refactoriser. Il n'y a pas moyen qu'une classe avec 40 dépendances respecte le principe de responsabilité unique ou le principe d'ouverture/fermeture.

4voto

wujek.oczko Points 31

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

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

Cette réponse est FAUSSE - car POUR CHAQUE TYPE D'INJECTION SPRING UTILISE LA RÉFLEXION ! Utilisez votre IDE, placez un point d'arrêt sur le setter / 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 exemple quand l'injection par champ est meilleure. Si vous utilisez l'injection par champ dans des tests d'intégration qui configurent le contexte Spring - l'argument de la testabilité de la classe est également invalide - à moins que vous ne vouliez écrire ensuite des tests pour vos tests d'intégration ;)

0 votes

Pouvez-vous s'il vous plaît préciser sur les trois approches d'injection utilisant la réflexion ? J'ai placé un point d'arrêt pour l'injection du constructeur mais je n'ai pas repéré quelque chose de similaire à la réflexion.

0 votes

:13, Bleh (com.wujq.cameldemo) newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect) newInstance:62, NativeConstructorAccessorImpl (sun.reflect) newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect) newInstance:423, Constructor (java.lang.reflect) Ceci est la pile d'appels à partir de l'injection de type constructeur bean - entrées topn.

3voto

pramodgautam Points 17
Champ
@Autowired
private DependencyA dependanceA;

@Autowired
private DependencyB dependanceB;

@Autowired
private DependencyC dependanceC;

Qu'est-ce qui ne va pas ? Comme vous pouvez le constater, la variante Champ est très agréable. Elle est très courte, concise, il n'y a pas de code redondant. Le code est facile à lire et à naviguer. Votre classe peut simplement se concentrer sur l'essentiel et n'est pas polluée par le code redondant de l'injection de dépendances. Vous n'avez qu'à placer l'annotation @Autowired au-dessus des champs et c'est tout. Pas besoin de constructeurs spéciaux ou de setters juste pour que le conteneur d'injection de dépendances fournisse vos dépendances. Java étant déjà verbeux, chaque opportunité de raccourcir votre code est la bienvenue, n'est-ce pas ?

Violation du principe de responsabilité unique

Il est très facile d'ajouter de nouvelles dépendances. Peut-être trop facile. Il n'y a pas de problème à ajouter six, dix ou même une douzaine de dépendances. Lorsque vous utilisez des constructeurs pour l'injection de dépendances, à un certain point, le nombre de paramètres du constructeur devient trop élevé et il est immédiatement évident qu'il y a un problème. Avoir trop de dépendances signifie généralement que la classe a trop de responsabilités. Cela peut constituer une violation du principe de responsabilité unique et de la séparation des préoccupations, et c'est un bon indicateur que la classe nécessite une inspection approfondie et éventuellement une refonte. Il n'y a pas de tel avertissement lorsque l'injection se fait directement dans les champs, car cette approche peut être mise à l'échelle indéfiniment.

Masquage des dépendances

L'utilisation d'un conteneur d'injection de dépendances signifie que la classe n'est plus responsable de la gestion de ses propres dépendances. La responsabilité d'obtenir les dépendances est extraite de la classe. Quelqu'un d'autre est désormais responsable de fournir les dépendances - le conteneur d'injection de dépendances ou les affecter manuellement dans les tests. Lorsque la classe n'est plus responsable de l'obtention de ses dépendances, elle doit les communiquer clairement en utilisant une interface publique - des méthodes ou des constructeurs. De cette manière, il est clair de ce dont la classe a besoin et aussi si c'est optionnel (setters) ou obligatoire (constructeurs).

Accouplement au conteneur d'injection de dépendances

Une des idées centrales des frameworks d'injection de dépendances est que la classe gérée ne doit pas dépendre du conteneur d'injection de dépendances utilisé. En d'autres termes, elle devrait être juste un POJO simple, qui peut être instancié indépendamment, à condition que vous lui passiez toutes les dépendances requises. De cette manière, vous pouvez l'instancier dans un test unitaire sans démarrer le conteneur d'injection de dépendances et le tester séparément (avec un conteneur, ce serait plus un test d'intégration). S'il n'y a pas d'accouplement au conteneur, vous pouvez utiliser la classe soit comme gérée, soit comme non-gérée, voire passer à un nouveau framework d'injection de dépendances.

Cependant, lors de l'injection directe dans les champs, vous ne fournissez aucun moyen direct d'instancier la classe avec toutes ses dépendances requises. Cela signifie :

Il y a un moyen (en appelant le constructeur par défaut) de créer un objet en utilisant new dans un état où il lui manque certains de ses collaborateurs obligatoires et son utilisation entraînera la NullPointerException. Une telle classe ne peut pas être réutilisée en dehors des conteneurs d'injection de dépendances (tests, autres modules), car il n'y a aucun moyen, sauf la réflexion, de lui fournir ses dépendances requises. Immutabilité Contrairement au constructeur, l'injection de champ ne peut pas être utilisée pour attribuer des dépendances à des champs finals, rendant ainsi vos objets mutables.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X