Pourquoi ApplicationContext.getBean de Spring est-il considéré comme mauvais?

StackOverflow https://stackoverflow.com/questions/812415

  •  03-07-2019
  •  | 
  •  

Question

J'ai posé une question générale sur le printemps: diffusion automatique de beans du printemps et plusieurs personnes ont répondu. l'appel de ApplicationContext.getBean () de Spring doit être évité autant que possible. Pourquoi donc?

Sinon, comment puis-je accéder aux beans que j'ai configurés pour que Spring soit créés?

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

Amendement

J'accepte la réponse ci-dessous, mais voici une autre solution prise par Martin Fowler qui discute des mérites de la dépendance. Injection ou utilisation d'un service de localisation (ce qui revient essentiellement à appeler un ApplicationContext.getBean () encapsulé).

En partie, déclare Fowler, " Avec le localisateur de service, la classe d'application le demande [le service] explicitement par un message au localisateur. Avec l'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 de contrôle est une caractéristique commune des frameworks, mais c'est quelque chose qui a un prix. Cela a tendance à être difficile à comprendre et à créer des problèmes lorsque vous essayez de déboguer. Donc dans l'ensemble, je préfère l'éviter [Inversion of Control] à moins que j'en ai besoin. Cela ne veut pas dire que c’est une mauvaise chose, mais seulement que je pense qu’elle doit se justifier par une alternative plus simple. "

Était-ce utile?

La solution

Je l’ai mentionné dans un commentaire sur l’autre question, mais l’idée même de l’inversion du contrôle est de ne laisser aucune de vos classes savoir ou s’occuper de la façon dont elles obtiennent les objets dont elles dépendent . Cela facilite le changement du type d'implémentation d'une dépendance donnée que vous utilisez à tout moment. Cela facilite également le test des classes, car vous pouvez fournir une implémentation factice de dépendances. Enfin, cela rend les classes plus simples et plus centrées sur leur responsabilité première.

L'appel de ApplicationContext.getBean () n'est pas une inversion de contrôle! Bien qu'il soit encore facile de changer le type d'implémentation configuré pour le nom de bean donné, la classe s'appuie désormais directement sur Spring pour fournir cette dépendance et ne peut l'obtenir autrement. Vous ne pouvez pas simplement créer votre propre implémentation factice dans une classe de test et la lui transmettre vous-même. Cela va fondamentalement à l'encontre de l'objectif de Spring en tant que conteneur d'injection de dépendance.

Partout où vous voulez dire:

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

vous devriez plutôt déclarer, par exemple, 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>

Spring injectera alors automatiquement myClass dans myOtherClass .

Déclarez tout de cette manière, et à la base, ayez 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. Lors de l'amorçage, dans votre méthode main , vous pouvez appeler applicationContext.getBean (" monApplication ") mais vous n'avez pas besoin d'appeler getBean () n'importe où ailleurs!

Autres conseils

Les raisons de préférer Service Locator à Inversion of Control (IoC) sont les suivantes:

  1. Service Locator est beaucoup plus facile à suivre pour votre code. IoC est "magique" mais les programmeurs de maintenance doivent comprendre vos configurations compliquées de Spring et toute la myriade d'emplacements pour comprendre comment vous avez câblé vos objets.

  2. IoC est terrible pour le débogage de problèmes de configuration. Dans certaines classes d’applications, l’application ne démarrera pas si elle est mal configurée et vous n’auriez peut-être pas la chance de voir ce qui se passe avec un débogueur.

  3. IoC est principalement basé sur XML (les annotations améliorent les choses mais il y a encore beaucoup de XML disponible). Cela signifie que les développeurs ne peuvent travailler sur votre programme que s'ils connaissent toutes les balises magiques définies par Spring. Il ne suffit plus de connaître Java. Cela gêne les programmeurs moins expérimentés (par exemple, le fait d'utiliser une solution plus compliquée est en fait d'une conception médiocre lorsqu'une solution plus simple, telle que Service Locator, répondra aux mêmes exigences). De plus, la prise en charge du diagnostic des problèmes XML est beaucoup plus faible que celle des problèmes Java.

  4. L'injection de dépendance est plus adaptée aux programmes plus importants. La plupart du temps, la complexité supplémentaire n’en vaut pas la peine.

  5. Souvent, Spring est utilisé au cas où "vous voudriez changer d'implémentation plus tard". Il existe d'autres moyens d'y parvenir sans la complexité de Spring IoC.

  6. Pour les applications Web (Java EE WARs), le contexte Spring est effectivement lié à la compilation (à moins que vous ne souhaitiez que les opérateurs analysent le contexte de la guerre éclatée). Vous pouvez créer des fichiers de propriétés Spring, mais avec les servlets, ils devront se trouver à un emplacement prédéterminé, ce qui signifie que vous ne pouvez pas déployer plusieurs servlets de la même heure sur la même boîte. Vous pouvez utiliser Spring avec JNDI pour modifier les propriétés au moment du démarrage du servlet, mais si vous utilisez JNDI pour des paramètres modifiables par l'administrateur, le besoin de Spring lui-même diminue (JNDI étant en fait un localisateur de service).

  7. Avec Spring, vous pouvez perdre le contrôle du programme si Spring envoie vos méthodes. C'est pratique et convient à de nombreux types d'applications, mais pas à toutes. Vous devrez peut-être contrôler le déroulement du programme lorsque vous aurez besoin de créer des tâches (threads, etc.) lors de l'initialisation ou si vous avez besoin de ressources modifiables inconnues de Spring lorsque le contenu était lié à votre fichier WAR.

Spring est très utile pour la gestion des transactions et présente certains avantages. C’est juste que IoC peut être trop technique dans de nombreuses situations et créer une complexité injustifiée pour les responsables de la maintenance. N'utilisez pas automatiquement IoC sans penser aux moyens de ne pas l'utiliser au préalable.

Il est vrai qu'inclure la classe dans application-context.xml évite d'avoir à utiliser getBean. Cependant, même cela n’est vraiment pas nécessaire. Si vous écrivez une application autonome et que vous NE voulez PAS inclure votre classe de pilote dans application-context.xml, vous pouvez utiliser le code suivant pour que Spring mette automatiquement 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 cela plusieurs fois lorsque 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 le contexte d'application car cela ne fait pas réellement partie de l'application. Notez également que cela évite de rechercher le haricot en utilisant un nom de chaîne, ce que j'ai toujours pensé être moche.

L’un des avantages les plus intéressants de l’utilisation de Spring est qu’il n’est pas nécessaire de câbler vos objets ensemble. La tête de Zeus se déchire 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 en direz ClassIneed classINeed = (ClassIneed) ApplicationContext.getBean ("classIneed"); , moins vous obtiendrez de magie. Moins de code est presque toujours préférable. Si votre classe avait vraiment besoin d'un haricot ClassINeed, pourquoi ne l'avez-vous pas câblé?

Cela dit, il faut évidemment que le premier objet soit créé. Il n’ya rien de mal à ce que votre méthode principale obtienne un ou deux beans via getBean (), mais vous devriez l’éviter, car chaque fois que vous l’utilisez, vous n’utilisez pas vraiment toute la magie du printemps.

La motivation est d'écrire du code qui ne dépend pas explicitement de Spring. Ainsi, si vous choisissez de changer de conteneur, vous n'avez pas à réécrire le code.

Pensez au conteneur comme si quelque chose était invisible pour votre code, répondant magiquement à ses besoins, sans qu'on vous le demande.

L'injection de dépendance est un contrepoint au "localisateur de service". modèle. Si vous recherchez des dépendances par nom, vous pouvez également vous débarrasser du conteneur DI et utiliser quelque chose comme JNDI.

Utiliser @Autowired ou ApplicationContext.getBean () est vraiment la même chose. Dans les deux cas, vous obtenez le bean configuré dans votre contexte et dans les deux cas, votre code dépend du printemps. La seule chose à éviter est d'instancier votre ApplicationContext. Faites cela 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.

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

ApplicationContext.getBean () vous oblige à nommer un bean explicitement dans votre composant. Au lieu de cela, en utilisant IoC, votre configuration peut déterminer quel composant sera utilisé.

Cela vous permet de reconnecter facilement votre application avec différentes implémentations de composants ou de configurer les objets à tester de manière simple en fournissant des variantes fictives (par exemple, une DAO fictive afin de ne pas frapper une base de données lors des tests)

D'autres ont souligné le problème général (et constituent des réponses valables), mais je ferai simplement un commentaire supplémentaire: ce n'est pas que vous ne devriez JAMAIS le faire, mais plutôt le faire le moins possible.

Cela signifie généralement que cela se fait exactement une fois: lors du démarrage. Et puis, c’est juste pour accéder à la " racine " haricot, à travers lequel d'autres dépendances peuvent être résolues. Cela peut être un code réutilisable, comme un servlet de base (si vous développez des applications Web).

L'un des locaux de Spring est d'éviter le couplage . Définissez et utilisez les interfaces, DI, AOP et évitez d’utiliser ApplicationContext.getBean (): -)

Une des raisons est la testabilité. Disons 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 pouvez-vous 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?

Bien que vous dépendiez toujours de Spring (à cause des annotations), vous pouvez supprimer votre dépendance à Spring sans modifier le code (uniquement les définitions d'annotation) et le développeur de test n'a pas besoin de savoir comment Spring fonctionne (peut-être qu'il devrait de toute façon, mais cela permet de revoir et de tester le code séparément de ce que fait Spring)

Il est toujours possible de faire la même chose avec ApplicationContext. Cependant, vous devez vous moquer de ApplicationContext , qui est une interface énorme. Soit vous avez besoin d’une implémentation factice, soit 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 une possibilité, mais je pense que la plupart des gens conviendront que la première option est plus élégante et simplifie le test.

La seule option qui pose vraiment problème est celle-ci:

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

Cela nécessite des efforts considérables, sinon votre bean tentera de se connecter à stackoverflow à chaque test. Et dès que vous rencontrez une défaillance du réseau (ou que les administrateurs de stackoverflow vous bloquent en raison d'un taux d'accès excessif), les tests échouent de manière aléatoire.

En conclusion, je ne dirais donc pas que l’utilisation directe de ApplicationContext est automatiquement une erreur et doit être évitée à tout prix. Toutefois, s’il existe de meilleures options (et dans la plupart des cas), utilisez les meilleures options.

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

D'autres ont mentionné l'utilisation de getBean () dans main () pour extraire le fichier "main". haricot pour un programme autonome.

Une autre utilisation que j'ai faite de getBean () concerne les situations dans lesquelles une configuration utilisateur interactive détermine la constitution du bean dans une situation particulière. Ainsi, par exemple, une partie du système d’amorçage parcourt une table de base de données en utilisant getBean () avec une définition de bean scope = 'prototype', puis en définissant des propriétés supplémentaires. Vraisemblablement, il existe une interface utilisateur qui ajuste la table de base de données qui serait plus conviviale que d'essayer de (ré) écrire le XML de contexte d'application.

Il y a un autre moment où utiliser getBean est logique. Si vous reconfigurez un système qui existe déjà, les dépendances ne sont pas explicitement appelées dans les fichiers de contexte Spring. Vous pouvez démarrer le processus en plaçant des appels à getBean, de sorte que vous n'ayez pas à tout connecter en même temps. De cette façon, vous pouvez construire lentement votre configuration de ressort en mettant chaque pièce en place au fil du temps et en alignant correctement les embouts. Les appels à getBean seront éventuellement remplacés, mais si vous comprenez la structure du code ou si vous en manquez, vous pouvez lancer le processus de câblage de plus en plus de beans et utiliser de moins en moins d'appels pour getBean.

Cependant, il existe encore des cas où vous avez besoin du modèle de localisateur de service. Par exemple, j'ai un bean controller, ce contrôleur peut avoir des beans de service par défaut, qui peuvent être une dépendance injectée par la configuration. Bien qu'il puisse également y avoir de nombreux services supplémentaires ou nouveaux que ce contrôleur peut invoquer maintenant ou plus tard, ce dernier a alors besoin du localisateur de service pour récupérer les beans de service.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top