Obtenir correctement Spring Bean à partir d'un contexte d'application tiers

Bonjour, Khabrovites!

Dans cet article, je propose de discuter de l'un des problèmes que l'on rencontre souvent dans les projets utilisant le framework Spring. Le problème décrit dans cet article se pose en raison d'une des erreurs typiques dans les configurations de ressort. Pas besoin d'essayer de faire une telle erreur dans la configuration, et donc cette erreur est assez courante.

Énoncé du problème


Le problème présenté dans cet article est lié à la configuration incorrecte des beans dans le contexte d'application actuel, qui sont extraits d'autres contextes d'application. Un tel problème peut survenir dans une grande application industrielle, qui se compose de nombreux pots, chacun ayant son propre contexte d'application contenant des haricots de printemps.

En raison d'une configuration incorrecte, nous obtenons plusieurs copies de beans avec un état imprévisible, même s'ils ont la portée singleton. De plus, une copie irréfléchie des beans peut conduire au fait que plus d'une douzaine de copies de tous les beans de n'importe quel bocal seront créées dans l'application, ce qui est lourd de problèmes de performances d'application et d'une augmentation du temps de démarrage de l'application.

Un exemple d'utilisation d'un bean à partir d'un contexte d'application externe dans le courant


Imaginez que nous développons dans l'un des modules d'application, dans lequel il existe de nombreux autres modules, et que chacun des modules a son propre contexte d'application. Une telle application doit avoir un module dans lequel des instances du contexte d'application de tous les modules d'application sont créées.



Supposons que, dans le contexte d'application de l'un des modules externes, une instance du bean de la classe NumberGenerator soit créée, que nous voulons obtenir dans notre module. Supposons également que la classe NumberGenerator se trouve dans le package org.example.kruchon.generators, qui stocke certaines classes qui génèrent des valeurs.



Ce bean a un état - le champ nombre entier.

package org.example.kruchon.calculators public class NumberGenerator { private int count = 0; public synchronized int next() { return count++; } } 

Une instance de ce bean est créée dans la sous-configuration GeneratorsConfiguration.

 @Configuration public class GeneratorsConfiguration { @Bean public NumberGenerator numberGenerator() { return new NumberGenerator(); } ... } 

Toujours dans le contexte d'application externe, il existe une configuration principale dans laquelle toutes les sous-configurations du module externe sont importées.

 @Configuration @Import({GeneratorsConfiguration.class, ...}) public class ExternalContextConfiguration { ... } 

Je vais maintenant donner quelques exemples dans lesquels le bean singleton de la classe NumberGenerator est mal configuré dans la configuration du contexte d'application actuel.

Configuration incorrecte 1. Importation de la configuration principale du contexte d'application externe


La pire décision qui puisse être.

 @Configuration @Import(ExternalContextConfiguration.class) public class CurrentContextConfiguration { ... } 

  • L'application recrée toutes les instances de beans à partir du contexte d'application externe. En d'autres termes, une copie de l'ensemble du module externe est créée, ce qui affecte la consommation de mémoire, les performances et le temps de démarrage de l'application.
  • Obtenez une copie de NumberGenerator dans le contexte d'application actuel. Une copie de NumberGenerator a sa propre valeur pour le champ count, incompatible avec la première instance de NumberGenerator. Cette incohérence entraîne des erreurs difficiles à déboguer dans l'application.

Configuration incorrecte 2. Importer la sous-configuration du contexte d'application externe


La deuxième option est incorrecte et souvent rencontrée dans la pratique.

 @Configuration @Import(GeneratorsConfiguration.class) public class CurrentContextConfiguration { ... } 

Dans ce mode de réalisation, une copie complète du module externe n'est plus créée, cependant, nous obtenons à nouveau le deuxième bean de la classe NumberGenerator.

Configuration incorrecte 3. Recherchez l'injection directement dans le bean, où nous voulons utiliser NumberGenerator


 public class OrderFactory { private final NumberGenerator numberGenerator; public OrderFactory() { ApplicationContext externalApplicationContext = getExternalContext(); numberGenerator = externalApplicationContext.getBean(NumberGenerator.class); } public Order create() { Order order = new Order(); int id = numberGenerator.next(); order.setId(id); order.setCreatedDate(new Date()); return order; } private ApplicationContext getExternalContext(){ ... } } 

Dans cette méthode, la duplication d'un bean ayant la portée singleton peut être considérée comme résolue. En effet, maintenant nous réutilisons le bean à partir d'un autre contexte d'application et ne le recréons pas!

Mais de cette façon:

  1. Complique la classe développée et ses tests unitaires.
  2. Exclut l'implémentation automatique du bean de la classe NumberGenerator dans les beans du module actuel.
  3. Il n'est pas courant d'utiliser lookUp pour injecter un bean singleton dans les cas généraux.

Par conséquent, une telle solution ressemble plus à une solution de contournement maladroite qu’une solution rationnelle à un problème.

Considérez comment configurer correctement un bean à partir d'un contexte d'application externe.

Solution 1. Obtenez le bean du contexte d'application externe dans la configuration


Cette méthode est très similaire au 3e exemple d'une configuration incorrecte avec une différence: nous obtenons un bean en faisant une recherche à partir du contexte externe dans la configuration, et pas directement dans le bean.

 @Configuration public class CurrentContextConfiguration { @Bean public NumberGenerator numberGenerator() { ApplicationContext externalApplicationContext = getExternalContext(); return externalApplicationContext.getBean(NumberGenerator.class); } private ApplicationContext getExternalContext(){ ... } } 

Nous pouvons maintenant intégrer automatiquement ce bean dans des beans à partir de notre propre module.

Solution 2. Rendre le parent du contexte d'application externe


Il est probable que la fonctionnalité du module actuel étend la fonctionnalité de l'externe. Il peut y avoir un cas où dans l'un des modules externes des beans auxiliaires communs à l'application entière sont développés, et dans d'autres modules ces beans sont utilisés. Dans ce cas, il est logique d'indiquer que le module externe est parent du précédent. Dans ce cas, tout le bean du module parent peut être utilisé dans le module actuel, puis le bean du module parent n'a pas besoin d'être configuré dans la configuration du contexte d'application actuel.

Il est possible de spécifier une relation parent lors de la création d'une instance de contexte à l'aide du constructeur avec le paramètre parent:

 public AbstractApplicationContext(ApplicationContext parent) { ... } 

Ou utilisez le setter:

 public void setParent(ApplicationContext parent) { ... } 

Si le contexte d'application est déclaré en xml, nous pouvons utiliser le constructeur:

 public ClassPathXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { ... } 

Conclusion


Par conséquent, soyez prudent lorsque vous configurez des beans ressort, suivez les recommandations de l'article et essayez de ne pas copier les beans qui ont une portée singleton. Je me ferai un plaisir de répondre à vos questions!

Source: https://habr.com/ru/post/fr472716/


All Articles