我问了一个常见的Spring问题:自动转换Spring bean,很多人回答说应该尽可能避免调用Spring的ApplicationContext.getBean()。为什么呢?

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

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

修正案

我接受下面的答案,但这里有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."


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

你越是说ClassINeed ClassINeed = (ClassINeed)ApplicationContext.getBean(" ClassINeed ");,你得到的魔法就越少。代码越少越好。如果您的类确实需要ClassINeed bean,为什么不直接将它连接进来呢?

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

其思想是依赖于依赖注入(控制反转,IoC)。也就是说,您的组件已经配置了所需的组件。这些依赖关系是注入的(通过构造函数或设置函数)——你不能自己得到。

getbean()要求您在组件中显式地命名bean。相反,通过使用IoC,您的配置可以确定将使用什么组件。

这让你可以轻松地用不同的组件实现重新连接应用程序,或者通过提供模拟变量(例如,模拟DAO,这样你就不会在测试期间碰到数据库)以一种直接的方式配置测试对象。

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

把容器想象成代码看不见的东西,神奇地提供它的需要,而不需要被要求。

依赖注入是“服务定位器”模式的对应。如果您打算按名称查找依赖项,那么您也可以摆脱DI容器,使用JNDI之类的东西。

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

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

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

调用ApplicationContext.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会自动将myClass注入到myOtherClass中。

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

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

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

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

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

我使用getBean()的另一种情况是交互用户配置为特定情况确定bean组成。因此,例如,引导系统的一部分使用带scope='prototype' bean定义的getBean()循环遍历数据库表,然后设置其他属性。据推测,有一种调整数据库表的UI比试图(重新)编写应用程序上下文XML更友好。

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

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

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

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

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. 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. 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. Dependency injection is more suited to larger programs. Most of the time the additional complexity is not worth it. 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. 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). 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.

Spring非常适合事务管理,并且有一些优点。只是IoC在许多情况下会过度设计,给维护者带来不必要的复杂性。不要在没有考虑不使用IoC的情况下自动使用它。

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

使用@Autowired或ApplicationContext.getBean()实际上是一回事。通过这两种方式,您可以获得在您的上下文中配置的bean,并且通过这两种方式,您的代码都依赖于spring。 你唯一应该避免的是实例化你的ApplicationContext。只做一次!换句话说,一条线就像

ApplicationContext context = new ClassPathXmlApplicationContext("AppContext.xml");

在应用程序中只应使用一次。

There is another time when using getBean makes sense. If you're reconfiguring a system that already exists, where the dependencies are not explicitly called out in spring context files. You can start the process by putting in calls to getBean, so that you don't have to wire it all up at once. This way you can slowly build up your spring configuration putting each piece in place over time and getting the bits lined up properly. The calls to getBean will eventually be replaced, but as you understand the structure of the code, or lack there of, you can start the process of wiring more and more beans and using fewer and fewer calls to getBean.

原因之一是可测试性。假设你有这样一个类:

interface HttpLoader {
    String load(String url);
}
interface StringOutput {
    void print(String txt);
}
@Component
class MyBean {
    @Autowired
    MyBean(HttpLoader loader, StringOutput out) {
        out.print(loader.load("http://stackoverflow.com"));
    }
}

如何测试这个bean?例如:

class MyBeanTest {
    public void creatingMyBean_writesStackoverflowPageToOutput() {
        // setup
        String stackOverflowHtml = "dummy";
        StringBuilder result = new StringBuilder();

        // execution
        new MyBean(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get, result::append);

        // evaluation
        assertEquals(result.toString(), stackOverflowHtml);
    }
}

容易,对吧?

当您仍然依赖于Spring(由于注释)时,您可以在不更改任何代码(只更改注释定义)的情况下删除对Spring的依赖,并且测试开发人员不需要了解Spring的工作原理(也许他应该知道,但是它允许将代码与Spring的工作分开检查和测试)。

在使用ApplicationContext时仍然可以做同样的事情。但是你需要模拟ApplicationContext,这是一个巨大的接口。你要么需要一个虚拟的实现,要么你可以使用一个mock框架,比如Mockito:

@Component
class MyBean {
    @Autowired
    MyBean(ApplicationContext context) {
        HttpLoader loader = context.getBean(HttpLoader.class);
        StringOutput out = context.getBean(StringOutput.class);

        out.print(loader.load("http://stackoverflow.com"));
    }
}
class MyBeanTest {
    public void creatingMyBean_writesStackoverflowPageToOutput() {
        // setup
        String stackOverflowHtml = "dummy";
        StringBuilder result = new StringBuilder();
        ApplicationContext context = Mockito.mock(ApplicationContext.class);
        Mockito.when(context.getBean(HttpLoader.class))
            .thenReturn(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get);
        Mockito.when(context.getBean(StringOutput.class)).thenReturn(result::append);

        // execution
        new MyBean(context);

        // evaluation
        assertEquals(result.toString(), stackOverflowHtml);
    }
}

这是很有可能的,但我认为大多数人会同意第一种选择更优雅,使测试更简单。

唯一真正有问题的选项是这个:

@Component
class MyBean {
    @Autowired
    MyBean(StringOutput out) {
        out.print(new HttpLoader().load("http://stackoverflow.com"));
    }
}

测试这个需要付出巨大的努力,否则您的bean将在每次测试时尝试连接到stackoverflow。一旦出现网络故障(或者stackoverflow的管理员由于访问速率过高而阻止了您),您的测试就会随机失败。

因此,作为结论,我不会说直接使用ApplicationContext是自动错误的,应该不惜一切代价避免。然而,如果有更好的选择(大多数情况下都有),那么就使用更好的选择。

你应该使用:ConfigurableApplicationContext而不是for ApplicationContext