为什么Spring的ApplicationContext.getBean被认为是坏的?

问题

我问了一个普通的Spring问题:Auto-cast Spring Beans并且有多个人回应,尽可能避免调用Spring的ApplicationContext.getBean()。这是为什么?

我还应该如何获得我配置Spring创建的bean的权限?

我在非Web应用程序中使用Spring,并计划访问sharedApplicationContextobjectas described by LiorH
修正案
我接受下面的答案,但这是Martin Fowler的另一个选择,他是discusses the merits of Dependency Injection vs. using a Service Locator(这与调用一个包装的ApplicationContext.getBean()基本相同)。

在某种程度上,Fowler说:"使用服务定位器,应用程序类通过消息向定位器显式请求它[服务]。注入时没有明确的请求,服务出现在应用程序类中 - 因此控制反转。控制反转是框架的一个共同特征,但它是有代价的。它往往难以理解并在你尝试调试时导致问题。所以总的来说我更愿意避免它[控制反转]除非我需要它。这并不是说它是一件坏事,只是因为我认为它需要通过更直接的替代方案来证明自己。"


#1 热门回答(173 赞)

我在另一个问题的评论中提到了这一点,但是控制反转的整个想法是让你的班级知道或关心他们如何获得他们依赖的对象。这样可以轻松更改你在任何时候使用的给定依赖项的实现类型。它还使类易于测试,因为你可以提供依赖项的模拟实现。最后,它使得classessimplerand更专注于他们的核心责任。

CallingApplicationContext.getBean()不是反转控制!虽然更改为给定bean名称配置的实现仍然很容易,但是现在该类直接依赖于Spring来提供该依赖性,并且无法以任何其他方式获取它。你不能只在测试类中创建自己的模拟实现,并自己将其传递给它。这基本上违背了Spring作为依赖注入容器的目的。

你想说的任何地方:

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

例如,你应该声明一个方法:

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

然后在你的配置中:

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

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

然后Spring会自动注入myClassintomyOtherClass

以这种方式声明一切,并在它的根部都有类似的东西:

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

MyApplication是最核心的类,至少间接依赖于程序中的每个其他服务。在引导时,在你的main方法中,你可以拨打applicationContext.getBean("myApplication"),但你不需要在其他地方拨打getBean()


#2 热门回答(53 赞)

更喜欢服务定位器而不是控制反转(IoC)的原因是:

  • 服务定位器让其他人更容易在你的代码中跟踪。 IoC是"神奇的",但维护程序员必须了解你的复杂Spring配置和所有无数的位置,以弄清楚如何连接对象。
  • IoC很难调试配置问题。在某些类别的应用程序中,如果配置错误,应用程序将无法启动,你可能无法逐步完成调试器的操作。
  • IoC主要基于XML(Annotations改进了一些东西,但仍然有很多XML)。这意味着开发人员无法处理你的程序,除非他们知道Spring定义的所有魔术标记。再也不懂Java了。这阻碍了经验较少的程序员(即,当更简单的解决方案,例如服务定位器,将满足相同的要求时,使用更复杂的解决方案实际上是糟糕的设计)。此外,对诊断XML问题的支持远远弱于对Java问题的支持。
  • 依赖注入更适合更大的程序。大多数情况下,额外的复杂性是不值得的。
  • 通常使用Spring以防"以后可能想要更改实现"。如果没有Spring IoC的复杂性,还有其他方法可以实现这一目标。
  • 对于Web应用程序(Java EE WAR),Spring上下文在编译时实际上是绑定的(除非你希望运算符在爆炸的战争中围绕上下文进行grub)。你可以使Spring使用属性文件,但是使用servlet属性文件需要位于预定位置,这意味着你无法在同一个盒子上同时部署多个servlet。你可以使用Spring和JNDI在servlet启动时更改属性,但如果你使用JNDI作为管理员可修改的参数,则Spring本身的需求会减少(因为JNDI实际上是一个服务定位器)。
  • 如果Spring调度到你的方法,使用Spring可能会丢失程序控制。这很方便,适用于许多类型的应用程序,但不是全部。当你需要在初始化期间创建任务(线程等)时,你可能需要控制程序流,或者需要可修改的资源,Spring在内容绑定到WAR时不知道这些资源。

Spring非常适合事务管理并具有一些优势。只是IoC在许多情况下可能过度工程化并为维护者带来无根据的复杂性。不考虑先不使用IoC的方法,不要自动使用IoC。


#3 热门回答(24 赞)

确实,在application-context.xml中包含类可以避免使用getBean。然而,即使这实际上也是不必要的。如果你正在编写一个独立的应用程序,并且你不希望在application-context.xml中包含你的驱动程序类,则可以使用以下代码让Spring自动装配驱动程序的依赖项:

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;    
    }
}

当我有一些需要使用我的应用程序的某些方面(例如用于测试)的独立类时,我需要这样做几次,但我不想将它包含在应用程序上下文中,因为它不是实际上是应用的一部分。另请注意,这避免了使用String名称查找bean的需要,我一直认为这很难看。