Pourquoi L'ApplicationContext de Spring.getBean considéré comme mauvais?

j'ai posé une question générale de printemps: graines de printemps auto-moulées et j'ai demandé à plusieurs personnes de répondre que l'appel du printemps ApplicationContext.getBean() devrait être évité autant que possible. Pourquoi est-ce?

comment accéder aux fèves que J'ai configuré pour créer?

j'utilise Spring dans une application non-web et avais prévu d'accéder à un ApplicationContext objet "partagé 151940920" comme décrit par LiorH .

amendement

j'accepte la réponse ci-dessous, mais voici un autre point de vue de Martin Fowler qui discute des avantages de L'Injection de dépendance par rapport à l'utilisation d'un Localisateur de Service (qui est essentiellement la même chose que d'appeler un ApplicationContext.getBean() enveloppé ).

en partie, Fowler déclare, " avec Localisateur de service la classe d'application le demande [le service] explicitement par un message pour le localisateur. Avec injection il n'y a pas de demande explicite, le service apparaît dans la classe d'application - d'où l'inversion du contrôle. L'Inversion du contrôle est une caractéristique commune des cadres, mais c'est quelque chose qui a un prix. Il a tendance à être difficile à comprendre, et conduit à des problèmes lorsque vous essayez de déboguer. Donc, dans l'ensemble, je préfère l'éviter [Inversion du contrôle] sauf si j'en ai besoin. Cela ne veut pas dire que c'est une mauvaise chose, juste que je pense qu'il a besoin de se justifier sur le plus alternative simple. "

226
demandé sur Community 2009-05-01 21:41:25

13 réponses

j'ai mentionné ceci dans un commentaire sur l'autre question, mais l'idée générale de L'Inversion du contrôle est d'avoir aucune de vos classes ne sait ou ne se soucie comment ils obtiennent les objets dont ils dépendent . Cela rend facile de changer le type d'implémentation d'une dépendance donnée que vous utilisez à tout moment. Cela rend également les classes faciles à tester, car vous pouvez fournir des implémentations simulées de dépendances. Enfin, il rend les classes plus simple et plus concentré sur leur responsabilité principale.

appeler ApplicationContext.getBean() n'est pas une Inversion de contrôle! Bien qu'il soit encore facile de modifier la configuration de la mise en œuvre pour le nom bean donné, la classe s'appuie maintenant directement sur Spring pour fournir cette dépendance et ne peut pas l'obtenir d'une autre manière. Vous ne pouvez pas juste faire votre propre mise en œuvre simulée dans une classe de test et de passer cela à elle vous-même. Cela va à l'encontre de L'objectif de Spring en tant que conteneur d'injection de dépendances.

partout vous voulez dire:

MyClass myClass = applicationContext.getBean("myClass");

vous devriez plutôt, par exemple, déclarer une méthode:

public void setMyClass(MyClass myClass) {
   this.myClass = myClass;
}

et ensuite dans votre configuration:

<bean id="myClass" class="MyClass">...</bean>

<bean id="myOtherClass" class="MyOtherClass">
   <property name="myClass" ref="myClass"/>
</bean>
Le ressort

injectera alors automatiquement myClass dans myOtherClass .

Déclarer tout de cette façon, et à la racine de tout cela quelque chose comme:

<bean id="myApplication" class="MyApplication">
   <property name="myCentralClass" ref="myCentralClass"/>
   <property name="myOtherCentralClass" ref="myOtherCentralClass"/>
</bean>

MyApplication est la classe la plus centrale, et dépend au moins indirectement de tous les autres services de votre programme. En bootstrapping, dans votre méthode main , vous pouvez appeler applicationContext.getBean("myApplication") mais vous ne devriez pas avoir besoin d'appeler getBean() ailleurs!

179
répondu ColinD 2012-08-27 12:54:52

les raisons de préférer le Localisateur de Service à L'Inversion du contrôle (IoC) sont les suivantes:

  1. Localisateur de Service est beaucoup, beaucoup plus facile pour d'autres personnes à suivre dans votre code. Le CIO est "magique", mais les programmeurs de maintenance doivent comprendre vos configurations de ressorts compliqués et toutes les myriades d'emplacements pour comprendre comment vous avez câblé vos objets.

  2. le COI est terrible pour déboguer les problèmes de configuration. Dans certaines classes d'applications l'application ne démarre pas lorsqu'elle est mal configurée et il se peut que vous n'ayez pas la chance de passer à travers ce qui se passe avec un débogueur.

  3. le COI est principalement basé sur le XML (les Annotations améliorent les choses mais il y a encore beaucoup de XML là-bas). Cela signifie que les développeurs ne peuvent pas travailler sur votre programme à moins qu'ils ne connaissent toutes les étiquettes magiques définies par le printemps. Il ne suffit plus de connaître Java. Cela empêche moins d'expérience les programmeurs (ie. il est en fait de mauvaise conception d'utiliser une solution plus compliquée quand une solution plus simple, comme le Localisateur de Service, remplira les mêmes exigences). De plus, le support pour diagnostiquer les problèmes XML est beaucoup plus faible que le support pour les problèmes Java.

  4. L'injection de dépendance convient mieux aux programmes plus importants. La plupart du temps, la complexité n'est pas la peine.

  5. souvent le ressort est utilisé dans cas où vous "pourriez vouloir changer la mise en œuvre plus tard". Il existe d'autres moyens d'y parvenir sans la complexité du CIO de printemps.

  6. pour les applications web (Java EE WARs) le contexte du printemps est effectivement lié au moment de la compilation (à moins que vous ne vouliez que les opérateurs grugent autour du contexte dans la guerre éclatée). Vous pouvez faire des fichiers de propriétés Spring use, mais avec servlets les fichiers de propriétés devront être à un emplacement prédéterminé, ce qui signifie que vous ne pouvez pas déployer plusieurs servlets le même temps sur la même case. Vous pouvez utiliser Spring avec JNDI pour changer les propriétés au démarrage du servlet, mais si vous utilisez JNDI pour les paramètres modifiables par l'administrateur, le besoin de Spring lui-même diminue (puisque JNDI est effectivement un Localisateur de Service).

  7. avec le ressort, vous pouvez perdre le contrôle du programme si le ressort envoie à vos méthodes. C'est pratique et travaille pour de nombreux types d'applications, mais pas tous. Vous pouvez besoin de contrôler le flux du programme lorsque vous avez besoin de créer des tâches (threads, etc) pendant l'initialisation ou besoin de ressources modifiables que Spring ne savait pas quand le contenu était lié à votre guerre.

Printemps est très bonne pour la gestion des transactions et a quelques avantages. Il est juste que la COI peut être sur-ingénierie dans de nombreuses situations et introduire une complexité injustifiée pour les responsables. N'utilisez pas automatiquement le CIO sans penser à des moyens de ne pas l'utiliser en premier.

56
répondu Moa 2014-11-03 09:53:00

il est vrai que l'inclusion de la classe dans le contexte de l'application.xml évite le besoin d'utiliser getBean. Cependant, même ce qui est en fait inutile. Si vous écrivez une application autonome et que vous ne voulez pas inclure votre classe de pilotes dans application-context.xml, vous pouvez utiliser le code suivant pour que Spring autowire les dépendances du pilote:

public class AutowireThisDriver {

    private MySpringBean mySpringBean;    

    public static void main(String[] args) {
       AutowireThisDriver atd = new AutowireThisDriver(); //get instance

       ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                  "/WEB-INF/applicationContext.xml"); //get Spring context 

       //the magic: auto-wire the instance with all its dependencies:
       ctx.getAutowireCapableBeanFactory().autowireBeanProperties(atd,
                  AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);        

       // code that uses mySpringBean ...
       mySpringBean.doStuff() // no need to instantiate - thanks to Spring
    }

    public void setMySpringBean(MySpringBean bean) {
       this.mySpringBean = bean;    
    }
}

j'ai eu besoin de faire ceci quelques fois quand j'ai une sorte de classe autonome qui doit utiliser certains aspects de mon application (par exemple pour les tests) mais je ne veux pas l'inclure dans l'application-contexte, car il n'est pas réellement partie de l'application. Notez aussi que cela évite le besoin de chercher le haricot en utilisant un nom de chaîne, que j'ai toujours pensé laid.

24
répondu iftheshoefritz 2009-09-07 11:35:09

l'un des avantages les plus cool d'utiliser quelque chose comme le ressort est que vous n'avez pas à relier vos objets ensemble. La tête de Zeus s'ouvre et vos classes apparaissent, entièrement formées avec toutes leurs dépendances créées et connectées, selon les besoins. C'est magique et fantastique.

plus vous dites ClassINeed classINeed = (ClassINeed)ApplicationContext.getBean("classINeed"); , moins vous obtenez de magie. Moins de code, c'est presque toujours mieux. Si votre classe avait vraiment besoin d'un haricot de la classe, pourquoi ne pas l'avoir câblé?

cela dit, quelque chose doit évidemment créer le premier objet. Il n'y a rien de mal à ce que votre méthode principale acquière un haricot ou deux via getBean(), mais vous devriez l'éviter car chaque fois que vous l'utilisez, vous n'utilisez pas vraiment toute la magie du printemps.

19
répondu Brandon Yarbrough 2009-05-01 17:46:37

la motivation est d'écrire du code qui ne dépend pas explicitement du ressort. De cette façon, si vous choisissez de changer de conteneur, vous n'avez pas à réécrire de code.

pensez au conteneur comme quelque chose est invisible à votre code, magiquement pourvoir à ses besoins, sans être demandé.

L'injection de dépendance

est un contrepoint au motif" Localisateur de service". Si vous cherchez des dépendances par leur nom, vous pourriez aussi bien vous débarrasser des Di container et utiliser quelque chose comme JNDI.

16
répondu erickson 2009-05-01 17:48:12

en utilisant @Autowired ou ApplicationContext.getBean() est vraiment la même chose. Dans les deux cas, vous obtenez le bean qui est configuré dans votre contexte et dans les deux cas votre code dépend de spring. La seule chose que vous devez éviter est d'instancier votre ApplicationContext. Faire qu'une seule fois! En d'autres termes, une ligne comme

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

ne doit être utilisé qu'une seule fois dans votre application.

10
répondu easyplanner.cu.cc 2012-03-29 07:57:20

l'idée est que vous comptez sur l'injection de dépendance ( inversion du contrôle , ou IoC). C'est, vos composants sont configurés avec les composants dont ils ont besoin. Ces dépendances sont injecté (via le constructeur ou les setters) - vous ne obtenez pas alors vous-même.

ApplicationContext.getBean() exige que vous nommiez explicitement un haricot dans votre composant. Au lieu de cela, en utilisant IoC, votre configuration peut déterminer quel composant sera utiliser.

cela vous permet de réembaucher facilement votre application avec différentes implémentations de composants, ou de configurer des objets pour tester de manière simple en fournissant des variantes moquées (par exemple un DAO moqué pour que vous ne frappiez pas une base de données pendant les tests)

4
répondu Brian Agnew 2009-05-01 17:52:03

D'autres ont souligné le problème général (et sont des réponses valables), mais je vais juste ajouter un commentaire supplémentaire: ce n'est pas que vous ne devriez jamais le faire, mais plutôt que le faire aussi peu que possible.

cela signifie généralement qu'il est fait exactement une fois: pendant bootstrapping. Et puis c'est juste pour accéder à la "racine", à travers laquelle d'autres dépendances peuvent être résolues. Cela peut être du code réutilisable, comme le servlet de base (si vous développez des applications web).

4
répondu StaxMan 2009-05-01 18:03:41

Je n'ai trouvé que deux situations où getBean() était requis:

D'autres ont mentionné l'utilisation de getBean() dans main() pour récupérer le" main " bean pour un programme autonome.

un autre usage que j'ai fait de getBean() sont dans des situations où une configuration utilisateur interactive détermine la composition de bean pour une situation particulière. De sorte que, par exemple, une partie du système d'amorçage passe à travers une table de base de données en utilisant getBean () avec un scope= 'prototype' définition du haricot et définition de propriétés supplémentaires. Probablement, il y a un UI qui ajuste la table de base de données qui serait plus amical que d'essayer de (ré)écrire le contexte de l'application XML.

3
répondu nsayer 2009-05-01 18:24:38

l'un des locaux du ressort est à éviter accouplement . Définissez et utilisez les Interfaces, DI, AOP et évitez D'utiliser ApplicationContext.getBean (): -)

3
répondu sourcerebels 2009-05-01 23:09:14

cependant, il y a encore des cas où vous avez besoin du modèle de localisateur de service. par exemple, j'ai un bean controller, ce controller peut avoir des haricots de service par défaut, qui peuvent être des dépendances injectées par configuration. bien qu'il pourrait également y avoir de nombreux services supplémentaires ou nouveaux que ce contrôleur peut invoquer maintenant ou plus tard, qui ont alors besoin du Localisateur de service pour récupérer les grains de service.

2
répondu lwpro2 2010-09-27 09:45:45

il y a un autre moment où l'utilisation de getBean a du sens. Si vous reconfigurez un système qui existe déjà, où les dépendances ne sont pas explicitement appelées dans les fichiers de contexte du printemps. Vous pouvez commencer le processus en passant des appels à getBean, de sorte que vous n'avez pas à télégraphier tout d'un coup. De cette façon, vous pouvez lentement construire votre configuration de ressort en mettant chaque pièce en place au fil du temps et en obtenant les bits alignés correctement. Les appels à getBean seront éventuellement remplacés, mais comme vous comprenez la structure du code, ou l'absence de, vous pouvez commencer le processus de câblage de plus en plus de haricots et en utilisant de moins en moins d'appels à getBean.

2
répondu Tony Giaccone 2012-04-27 20:18:40

l'Une des raisons est la testabilité. Dites que vous avez cette classe:

interface HttpLoader {
    String load(String url);
}
interface StringOutput {
    void print(String txt);
}
@Component
class MyBean {
    @Autowired
    MyBean(HttpLoader loader, StringOutput out) {
        out.print(loader.load("http://stackoverflow.com"));
    }
}

Comment tester ce haricot? Par exemple: comme ceci:

class MyBeanTest {
    public void creatingMyBean_writesStackoverflowPageToOutput() {
        // setup
        String stackOverflowHtml = "dummy";
        StringBuilder result = new StringBuilder();

        // execution
        new MyBean(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get, result::append);

        // evaluation
        assertEquals(result.toString(), stackOverflowHtml);
    }
}

facile, non?

pendant que vous dépendez encore du ressort (en raison des annotations) vous pouvez supprimer votre dépendance sur le ressort sans changer aucun code (seulement les définitions d'annotation) et le développeur de test n'a pas besoin de savoir quoi que ce soit sur la façon dont le ressort fonctionne (peut-être il devrait de toute façon, mais il permet de réviser et tester le code séparément de ce que le ressort fait).

il est encore possible de faire la même chose en utilisant le contexte de la demande. Cependant alors vous devez vous moquer de ApplicationContext qui est une interface énorme. Vous avez besoin d'une implémentation factice ou vous pouvez utiliser un framework moqueur tel que Mockito:

@Component
class MyBean {
    @Autowired
    MyBean(ApplicationContext context) {
        HttpLoader loader = context.getBean(HttpLoader.class);
        StringOutput out = context.getBean(StringOutput.class);

        out.print(loader.load("http://stackoverflow.com"));
    }
}
class MyBeanTest {
    public void creatingMyBean_writesStackoverflowPageToOutput() {
        // setup
        String stackOverflowHtml = "dummy";
        StringBuilder result = new StringBuilder();
        ApplicationContext context = Mockito.mock(ApplicationContext.class);
        Mockito.when(context.getBean(HttpLoader.class))
            .thenReturn(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get);
        Mockito.when(context.getBean(StringOutput.class)).thenReturn(result::append);

        // execution
        new MyBean(context);

        // evaluation
        assertEquals(result.toString(), stackOverflowHtml);
    }
}

C'est tout à fait possible, mais je pense que la plupart des gens seraient d'accord que la première option est plus élégante et rend le test plus simple.

la seule option qui est vraiment un problème est celle-ci:

@Component
class MyBean {
    @Autowired
    MyBean(StringOutput out) {
        out.print(new HttpLoader().load("http://stackoverflow.com"));
    }
}

test ceci exige d'énormes efforts ou votre haricot va essayer de se connecter à stackoverflow sur chaque test. Et dès que vous avez une panne de réseau (ou que les administrateurs de stackoverflow vous bloquent en raison d'un taux d'accès excessif), vous aurez des tests d'échec aléatoires.

ainsi comme une conclusion je ne dirais pas que l'utilisation de la ApplicationContext directement est automatiquement erroné et doit être évitée à tout prix. Cependant, s'il y a de meilleures options (et il y en a dans la plupart des cas), utilisez les meilleures.

2
répondu yankee 2017-01-25 13:59:50