从第三方应用程序上下文正确获取Spring Bean

下午好,Khabrovites!

在本文中,我建议讨论使用Spring框架的项目中经常遇到的问题之一。 本文中描述的问题是由于弹簧配置中的典型错误之一引起的。 无需尝试在配置中犯这样的错误,因此此错误非常普遍。

问题陈述


本文中提出的问题与当前应用程序上下文中Bean的错误配置有关,这些错误配置是从其他应用程序上下文中获取的。 在由许多罐子组成的大型工业应用程序中可能会出现这样的问题,每个罐子都有自己的应用程序上下文,其中包含spring bean。

由于配置错误,即使具有作用域单例,我们也会获得状态处于不可预测状态的Bean的多个副本。 此外,对bean的漫不经心的复制会导致以下事实:在应用程序中将创建任何jar的所有bean的十几个副本,这充满了应用程序性能问题和应用程序启动时间的增加。

当前使用外部应用程序上下文中的Bean的示例


想象一下,我们正在一个应用程序模块中开发,其中有许多其他模块,并且每个模块都有自己的应用程序上下文。 这样的应用程序应该具有一个模块,在其中创建所有应用程序模块的应用程序上下文的实例。



假设在外部模块之一的应用程序上下文中,创建了NumberGenerator类的bean的实例,我们希望将其放入模块中。 还假设NumberGenerator类位于包org.example.kruchon.generators中,该包存储一些生成值的类。



这个bean有一个状态-int count字段。

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

在GeneratorsConfiguration子配置中创建此bean的实例。

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

同样在外部应用程序上下文中,有一个主要配置,其中导入了外部模块的所有子配置。

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

现在,我将给出一些示例,其中在当前应用程序上下文的配置中,NumberGenerator类的单例bean配置不正确。

错误的配置1.导入外部应用程序上下文的主要配置


可能是最糟糕的决定。

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

  • 该应用程序从外部应用程序上下文重新创建所有bean实例。 换句话说,将创建整个外部模块的副本,这将影响内存消耗,性能和应用程序启动时间。
  • 在当前应用程序上下文中获取NumberGenerator的副本。 NumberGenerator的副本具有其自己的count字段值,与NumberGenerator的第一个实例不一致。 这种不一致会导致应用程序中难以调试的错误。

错误的配置2.导入外部应用程序上下文的子配置


第二种选择是不正确的,在实践中经常遇到。

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

在此实施例中,不再创建外部模块的完整副本,但是,我们再次获得NumberGenerator类的第二个bean。

错误的配置3.直接在我们要使用NumberGenerator的bean中查找注入


 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(){ ... } } 

在这种方法中,具有作用域单例的bean的重复可以视为已解决。 确实,现在我们从另一个应用程序上下文中重用了Bean,并且没有重新创建它!

但是这样:

  1. 使开发的类及其单元测试变得复杂。
  2. 在当前模块的bean中排除NumberGenerator类的bean的自动实现。
  3. 通常情况下,不使用lookUp注入单例bean。

因此,这种解决方案更像是笨拙的解决方法,而不是合理地解决问题。

考虑如何从外部应用程序上下文中正确配置bean。

解决方案1.从配置中的外部应用程序上下文获取bean


此方法与不正确配置的第三个示例非常相似,只是有一个区别:我们通过从配置中的外部上下文进行lookUp而不是直接进入bean中来获取bean。

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

现在,我们可以从我们自己的模块中自动将此bean嵌入到bean中。

解决方案2.将外部应用程序上下文设为父级


当前模块的功能可能会扩展外部功能。 可能存在以下情况:在外部模块之一中开发了整个应用程序通用的辅助Bean,而在其他模块中使用了这些Bean。 在这种情况下,逻辑上表明外部模块是前一个模块的父模块。 在这种情况下,可以在当前模块中使用父模块中的所有bean,然后无需在当前应用程序上下文的配置中配置父模块bean

在使用带有parent参数的构造函数创建上下文实例时,可以指定父关系:

 public AbstractApplicationContext(ApplicationContext parent) { ... } 

或使用设置器:

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

如果应用程序上下文是在xml中声明的,则可以使用构造函数:

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

结论


因此,在配置Spring bean时要小心,遵循本文中给出的建议,并尽量不要复制具有作用域单例的bean。 我很高兴回答您的问题!

Source: https://habr.com/ru/post/zh-CN472716/


All Articles