Getting Spring Bean from third-party Application Context correctly

Good afternoon, Khabrovites!



In this article, I propose to discuss one of the problems that are often encountered in projects using the Spring framework. The problem described in this article arises due to one of the typical errors in spring configurations. No need to try to make such a mistake in the configuration, and therefore this error is quite common.



Wording problem



The problem presented in this article is related to the incorrect configuration of beans in the current application context, which are taken from other application contexts. Such a problem can arise in a large industrial application, which consists of many jar, each of which has its own application context containing spring beans.



As a result of incorrect configuration, we get several copies of beans with an unpredictable state, even if they have the scope singleton. Moreover, mindless copying of beans can lead to the fact that the application will create more than a dozen copies of all beans of any jar, which is fraught with application performance problems and an increase in application startup time.



An example of using a bean from an external application context in the current



Imagine that we are developing in one of the application modules, in which there are many other modules, and that each of the modules has its own application context. Such an application should have a module in which instances of the application context of all application modules are created.







Suppose, in the application context of one of the external modules, an instance of the bean of the NumberGenerator class is created, which we want to receive in our module. Also suppose that the NumberGenerator class is located in the package org.example.kruchon.generators, which stores some classes that generate values.







This bean has a state - the int count field.



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





An instance of this bean is created in the GeneratorsConfiguration subconfiguration.



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





Also in the external application context, there is a main configuration in which all subconfigurations of the external module are imported.



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





Now I will give some examples in which the singleton bean of the NumberGenerator class is configured incorrectly in the configuration of the current application context.



Incorrect configuration 1. Importing the main configuration of the external application context



The worst decision that could be.



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







Incorrect configuration 2. Import subconfiguration of external application context



The second option is incorrect and often encountered in practice.



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





In this option, a complete copy of the external module is no longer created, nevertheless, we again get the second bean of the NumberGenerator class.



Incorrect configuration 3. Look up injection directly into the bean, where we want to use 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(){ ... } }
      
      





In this method, the problem of duplicating a bean having the scope singleton can be considered resolved. After all, now we reuse the bean from another application context and do not re-create it!



But this way:



  1. Complicates the developed class and its unit testing.
  2. Excludes the automatic implementation of the bean of the NumberGenerator class in the beans of the current module.
  3. It is not customary to use lookUp to inject a singleton bean in general cases.


Therefore, such a solution is more like a clumsy workaround than a rational solution to a problem.



Consider how to properly configure a bean from an external application context.



Solution 1. Get bean from external application context in configuration



This method is very similar to the 3rd example of an incorrect configuration with one difference: we get a bean by making lookUp from the external context in the configuration, and not directly into the bean.



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





Now we can automatically embed this bean in beans from our own module.



Solution 2. Make the external application context parent



It is likely that the functionality of the current module extends the functionality of the external. There may be a case when in one of the external modules auxiliary beans common to the entire application are developed, and in other modules these beans are used. In this case, it is logical to indicate that the external module is parent to the previous one. In this case, all bean from the parent module can be used in the current module, and then the bean of the parent module does not need to be configured in the configuration of the current application context.



It is possible to specify a parent relationship when creating a context instance using the constructor with the parent parameter:



 public AbstractApplicationContext(ApplicationContext parent) { ... }
      
      





Or use the setter:



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





If the application context is declared in xml, we can use the constructor:



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





Conclusion



Thus, be careful when configuring spring beans, follow the recommendations in the article and try not to copy beans that have scope singleton. I will be glad to answer your questions!



All Articles