Java 为什么 Spring 的 ApplicationContext.getBean 被认为是不好的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/812415/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 19:46:24  来源:igfitidea点击:

Why is Spring's ApplicationContext.getBean considered bad?

javaspring

提问by Vinnie

I asked a general Spring question: Auto-cast Spring Beansand had multiple people respond that calling Spring's ApplicationContext.getBean()should be avoided as much as possible. Why is that?

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

How else should I gain access to the beans I configured Spring to create?

我还应该如何访问我配置 Spring 创建的 bean?

I'm using Spring in a non-web application and had planned on accessing a shared ApplicationContextobject as described by LiorH.

我在非 Web 应用程序中使用 Spring,并计划访问LiorH 描述的共享ApplicationContext对象。

Amendment

修正案

I accept the answer below, but here's an alternate take by Martin Fowler who discusses the merits of Dependency Injection vs. using a Service Locator(which is essentially the same as calling a wrapped ApplicationContext.getBean()).

我接受下面的答案,但这是 Martin Fowler 的另一种看法,他讨论了依赖注入与使用服务定位器(本质上与调用包装的相同ApplicationContext.getBean()的优点

In part, Fowler states, "With service locator the application class asks for it [the service] explicitly by a message to the locator. With injection there is no explicit request, the service appears in the application class - hence the inversion of control. Inversion of control is a common feature of frameworks, but it's something that comes at a price. It tends to be hard to understand and leads to problems when you are trying to debug. So on the whole I prefer to avoid it [Inversion of Control] unless I need it. This isn't to say it's a bad thing, just that I think it needs to justify itself over the more straightforward alternative."

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

采纳答案by ColinD

I mentioned this in a comment on the other question, but the whole idea of Inversion of Control is to have none of your classes know or care how they get the objects they depend on. This makes it easy to change what type of implementation of a given dependency you use at any time. It also makes the classes easy to test, as you can provide mock implementations of dependencies. Finally, it makes the classes simplerand more focused on their core responsibility.

我在另一个问题的评论中提到了这一点,但控制反转的整个想法是让您的任何类都不知道或关心它们如何获取它们所依赖的对象。这使得随时更改给定依赖项的实现类型变得容易。它还使类易于测试,因为您可以提供依赖项的模拟实现。最后,它使类更简单,更专注于它们的核心职责。

Calling ApplicationContext.getBean()is not Inversion of Control! While it's still easy to change what implemenation is configured for the given bean name, the class now relies directly on Spring to provide that dependency and can't get it any other way. You can't just make your own mock implementation in a test class and pass that to it yourself. This basically defeats Spring's purpose as a dependency injection container.

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

Everywhere you want to say:

到处都想说:

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

you should instead, for example, declare a method:

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

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

And then in your configuration:

然后在您的配置中:

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

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

Spring will then automatically inject myClassinto myOtherClass.

Spring会自动注入myClassmyOtherClass

Declare everything in this way, and at the root of it all have something like:

以这种方式声明所有内容,并在其根源处具有以下内容:

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

MyApplicationis the most central class, and depends at least indirectly on every other service in your program. When bootstrapping, in your mainmethod, you can call applicationContext.getBean("myApplication")but you should not need to call getBean()anywhere else!

MyApplication是最核心的类,并且至少间接依赖于程序中的所有其他服务。引导时,在您的main方法中,您可以调用,applicationContext.getBean("myApplication")但不需要getBean()在其他任何地方调用!

回答by Brandon Yarbrough

One of the coolest benefits of using something like Spring is that you don't have to wire your objects together. Zeus's head splits open and your classes appear, fully formed with all of their dependencies created and wired-in, as needed. It's magical and fantastic.

使用 Spring 之类的东西最酷的好处之一是您不必将对象连接在一起。Zeus 的头裂开,您的类出现了,根据需要创建并连接了它们的所有依赖项。这是神奇而奇妙的。

The more you say ClassINeed classINeed = (ClassINeed)ApplicationContext.getBean("classINeed");, the less magic you're getting. Less code is almost always better. If your class really needed a ClassINeed bean, why didn't you just wire it in?

你说的越多ClassINeed classINeed = (ClassINeed)ApplicationContext.getBean("classINeed");,你得到的魔法就越少。更少的代码几乎总是更好。如果你的班级真的需要一个 ClassINeed bean,你为什么不直接连接它呢?

That said, something obviously needs to create the first object. There's nothing wrong with your main method acquiring a bean or two via getBean(), but you should avoid it because whenever you're using it, you're not really using all of the magic of Spring.

也就是说,显然需要创建第一个对象。您的 main 方法通过 getBean() 获取一两个 bean 没有任何问题,但是您应该避免它,因为无论何时使用它,您并没有真正使用 Spring 的所有魔力。

回答by Brian Agnew

The idea is that you rely on dependency injection (inversion of control, or IoC). That is, your components are configured with the components they need. These dependencies are injected(via the constructor or setters) - you don't get then yourself.

这个想法是你依赖依赖注入(控制反转,或 IoC)。也就是说,您的组件配置了它们需要的组件。这些依赖项被注入(通过构造函数或设置器)——你自己没有得到。

ApplicationContext.getBean()requires you to name a bean explicitly within your component. Instead, by using IoC, your configuration can determine what component will be used.

ApplicationContext.getBean()要求您在组件中显式命名一个 bean。相反,通过使用 IoC,您的配置可以确定将使用哪个组件。

This allows you to rewire your application with different component implementations easily, or configure objects for testing in a straightforward fashion by providing mocked variants (e.g. a mocked DAO so you don't hit a database during testing)

这允许您轻松地使用不同的组件实现重新连接您的应用程序,或者通过提供模拟变体(例如,模拟 DAO 以便您在测试期间不会访问数据库)以简单的方式配置用于测试的对象

回答by erickson

The motivation is to write code that doesn't depend explicitly on Spring. That way, if you choose to switch containers, you don't have to rewrite any code.

动机是编写不显式依赖 Spring 的代码。这样,如果您选择切换容器,则不必重写任何代码。

Think of the container as something is invisible to your code, magically providing for its needs, without being asked.

把容器想象成对你的代码不可见的东西,它神奇地提供了它的需求,而不被问到。

Dependency injection is a counterpoint to the "service locator" pattern. If you are going to lookup dependencies by name, you might as well get rid of the DI container and use something like JNDI.

依赖注入是“服务定位器”模式的对立面。如果您要按名称查找依赖项,您不妨摆脱 DI 容器并使用 JNDI 之类的东西。

回答by StaxMan

Others have pointed to the general problem (and are valid answers), but I'll just offer one additional comment: it's not that you should NEVER do it, but rather that do it as little as possible.

其他人已经指出了普遍问题(并且是有效的答案),但我只想提供一个额外的评论:不是你不应该这样做,而是尽可能少做。

Usually this means that it is done exactly once: during bootstrapping. And then it's just to access the "root" bean, through which other dependencies can be resolved. This can be reusable code, like base servlet (if developing web apps).

通常这意味着它只执行一次:在引导期间。然后只是访问“root”bean,通过它可以解决其他依赖项。这可以是可重用的代码,如基本 servlet(如果开发 Web 应用程序)。

回答by nsayer

I've only found two situations where getBean() was required:

我只发现了两种需要 getBean() 的情况:

Others have mentioned using getBean() in main() to fetch the "main" bean for a standalone program.

其他人提到在 main() 中使用 getBean() 来获取独立程序的“主”bean。

Another use I have made of getBean() are in situations where an interactive user configuration determines the bean makeup for a particular situation. So that, for instance, part of the boot system loops through a database table using getBean() with a scope='prototype' bean definition and then setting additional properties. Presumably, there is a UI that adjusts the database table that would be friendlier than attempting to (re)write the application context XML.

我对 getBean() 的另一个用途是在交互式用户配置确定特定情况下的 bean 构成的情况下。例如,启动系统的一部分使用 getBean() 和 scope='prototype' bean 定义循环遍历数据库表,然后设置其他属性。据推测,有一个 UI 可以调整数据库表,这比尝试(重新)编写应用程序上下文 XML 更友好。

回答by sourcerebels

One of Spring premises is avoid coupling. Define and use Interfaces, DI, AOP and avoid using ApplicationContext.getBean() :-)

Spring 的前提之一是避免耦合。定义和使用接口、DI、AOP 并避免使用 ApplicationContext.getBean() :-)

回答by iftheshoefritz

It's true that including the class in application-context.xml avoids the need to use getBean. However, even that is actually unnecessary. If you are writing a standalone application and you DON'T want to include your driver class in application-context.xml, you can use the following code to have Spring autowire the driver's dependencies:

确实,在 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;    
    }
}

I've needed to do this a couple of times when I have some sort of standalone class that needs to use some aspect of my app (eg for testing) but I don't want to include it in application-context because it is not actually part of the app. Note also that this avoids the need to look up the bean using a String name, which I've always thought was ugly.

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

回答by Moa

Reasons to prefer Service Locator over Inversion of Control (IoC) are:

选择服务定位器而不是控制反转 (IoC) 的原因是:

  1. Service Locator is much, much easier for other people to following in your code. IoC is 'magic' but maintenance programmers must understand your convoluted Spring configurations and all the myriad of locations to figure out how you wired your objects.

  2. IoC is terrible for debugging configuration problems. In certain classes of applications the application will not start when misconfigured and you may not get a chance to step through what is going on with a debugger.

  3. IoC is primarily XML based (Annotations improve things but there is still a lot of XML out there). That means developers can't work on your program unless they know all the magic tags defined by Spring. It is not good enough to know Java anymore. This hinders less experience programmers (ie. it is actually poor design to use a more complicated solution when a simpler solution, such as Service Locator, will fulfill the same requirements). Plus, support for diagnosing XML problems is far weaker than support for Java problems.

  4. Dependency injection is more suited to larger programs. Most of the time the additional complexity is not worth it.

  5. Often Spring is used in case you "might want to change the implementation later". There are other ways of achieving this without the complexity of Spring IoC.

  6. For web applications (Java EE WARs) the Spring context is effectively bound at compile time (unless you want operators to grub around the context in the exploded war). You can make Spring use property files, but with servlets property files will need to be at a pre-determined location, which means you can't deploy multiple servlets of the same time on the same box. You can use Spring with JNDI to change properties at servlet startup time, but if you are using JNDI for administrator-modifiable parameters the need for Spring itself lessens (since JNDI is effectively a Service Locator).

  7. With Spring you can lose program Control if Spring is dispatching to your methods. This is convenient and works for many types of applications, but not all. You may need to control program flow when you need to create tasks (threads etc) during initialization or need modifiable resources that Spring didn't know about when the content was bound to your WAR.

  1. 其他人在您的代码中跟踪服务定位器要容易得多。IoC 是“魔法”,但维护程序员必须了解您错综复杂的 Spring 配置和所有无数位置,才能弄清楚您如何连接对象。

  2. IoC 对于调试配置问题来说很糟糕。在某些类别的应用程序中,如果配置错误,应用程序将无法启动,您可能没有机会逐步了解调试器正在发生的事情。

  3. IoC 主要是基于 XML 的(注解改进了一些东西,但仍然有很多 XML)。这意味着开发人员不能在你的程序上工作,除非他们知道 Spring 定义的所有魔法标签。再了解 Java 已经不够了。这阻碍了经验不足的程序员(即,当服务定位器等更简单的解决方案满足相同要求时,使用更复杂的解决方案实际上是糟糕的设计)。另外,对诊断 XML 问题的支持远弱于对 Java 问题的支持。

  4. 依赖注入更适合大型程序。大多数情况下,额外的复杂性是不值得的。

  5. 如果您“以后可能想要更改实现”,通常会使用 Spring。在没有 Spring IoC 的复杂性的情况下,还有其他方法可以实现这一点。

  6. 对于 Web 应用程序(Java EE WAR),Spring 上下文在编译时有效绑定(除非您希望运算符在爆炸战争中围绕上下文进行抓取)。您可以让 Spring 使用属性文件,但是对于 servlet,属性文件需要位于预先确定的位置,这意味着您不能在同一个盒子上同时部署多个 servlet。您可以使用带有 JNDI 的 Spring 在 servlet 启动时更改属性,但是如果您将 JNDI 用于管理员可修改的参数,则对 Spring 本身的需求就会减少(因为 JNDI 实际上是一个服务定位器)。

  7. 使用 Spring,如果 Spring 正在调度到您的方法,您可能会失去程序控制。这很方便,适用于许多类型的应用程序,但不是全部。当您需要在初始化期间创建任务(线程等)或需要 Spring 不知道内容何时绑定到您的 WAR 的可修改资源时,您可能需要控制程序流。

Spring is very good for transaction management and has some advantages. It is just that IoC can be over-engineering in many situations and introduce unwarranted complexity for maintainers. Do not automatically use IoC without thinking of ways of not using it first.

Spring对于事务管理非常好,有一些优势。只是 IoC 在许多情况下可能会过度设计,并为维护人员带来不必要的复杂性。不要在没有先考虑不使用它的方法的情况下自动使用 IoC。

回答by lwpro2

however, there are still cases where you need the service locator pattern. for example, i have a controller bean, this controller might have some default service beans, which can be dependency injected by configuration. while there could also be many additional or new services this controller can invoke now or later, which then need the service locator to retrieve the service beans.

但是,仍然存在需要服务定位器模式的情况。例如,我有一个控制器 bean,这个控制器可能有一些默认的服务 bean,它们可以通过配置进行依赖注入。虽然也可能有许多额外的或新的服务,这个控制器现在或以后可以调用,然后需要服务定位器来检索服务 bean。