16

我有一个相互自动装配的 Spring bean 图。高度简化的插图:

<context:annotation-config/>
<bean class="Foo"/>
<bean class="Bar"/>
<bean class="Baz"/>

...

public class Foo {
   @Autowired Bar bar;
   @Autowired Baz baz;
}

public class Bar {
   @Autowired Foo foo;
}

public class Baz {
   @Autowired Foo foo;
}

所有这些 bean 都没有指定范围,这意味着它们是单例(使它们显式单例不会改变任何东西,我已经尝试过)。

问题在于,在单个应用程序上下文实例之后,实例BarBaz包含. 这怎么可能发生?Foo

我试图创建 public no args 构造函数,Foo并且调试已确认Foo多次创建。所有这些创作的堆栈跟踪都在这里

我还尝试为 Spring 启用调试日志记录,在所有其他行中,得到以下内容:

DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'Foo'
DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'Foo'
DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'Foo'

我知道我的 bean 是相互交叉引用的,但我希望 Spring 框架尊重单例范围并初始化一个单例 bean,然后将它自动连接到任何想要它的人。

有趣的事实是,如果我使用带有访问器的老式private构造函数public static Foo getInstance,这工作得很好——在上下文设置期间不会抛出异常。

FWIW,我正在使用带有o.s.c.s.ClassPathXmlApplicationContext(String ...configLocations)构造函数的 Spring 版本 3.0.5(也尝试使用 3.1.2,结果相同)。

我可以轻松地将我的代码转换为使用静态初始化程序,但我想了解为什么 Spring 会以这种方式运行。这是一个错误吗?

编辑:一些额外的调查表明

  • 初始化应用程序上下文后,所有后续请求context.getBean(Foo.class) 始终返回相同的Foo.
  • @Autowired用 setter替换(这个 bean 大约 20 次使用)仍然会导致这个对象的多个构造,但是所有依赖项都注入了相同的引用。

对我来说,上面表明这是一个与@Autowired实现有关的 Spring 错误。如果我设法获得任何有用的信息,我将发布到 Spring 社区论坛并返回这里。

4

4 回答 4

13

如果您不小心使用 context:component-scan 注释,子上下文可以重新实例化相同的单例 bean(还有其他 Spring 上下文扫描注释,例如 MVC 和其他)。这是在 Web 应用程序中使用 Spring servlet 时的常见问题,请参阅为什么 DispatcherServlet 创建另一个应用程序上下文?

确保您没有在子上下文中重新扫描组件,或者您只扫描特定的包/注释并从根上下文组件扫描中排除所述包/注释。

于 2013-02-12T17:10:52.343 回答
1

出于某种原因,我们在集成测试和服务中也随机出现了这个问题(spring 版本 4.1.4,java 1.8)。

看起来可能有不止一个罪魁祸首 - 自动装配一开始似乎是造成这种情况的原因。

但是,我们通过确保为每个受影响的 bean 提供一个“id”字段来解决最一致的故障。

于 2016-06-29T11:50:12.027 回答
0

尝试使用setter注入而不是构造方法,看看它是否有效。在spring bean xml中将Bean A ref指定给Bean B,反之亦然。

于 2012-07-18T19:02:58.603 回答
0

我的 Spring 配置如下:

<context:annotation-config/>

<bean class="Bar" />
<bean class="Foo" />
<bean class="Baz" /> 

课程与您的课程相同

测试应用程序如下:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("META-INF/spring/testctx.xml");

        Foo foo = ctx.getBean(Foo.class);
        Baz baz = ctx.getBean(Baz.class);
        Bar bar = ctx.getBean(Bar.class);

        System.out.println(foo.equals(baz.foo));
        System.out.println(foo.equals(bar.foo));
        System.out.println(baz.equals(foo.baz));

        System.out.println(foo.baz.toString());
        System.out.println(baz.toString());
        System.out.println(foo.bar.toString());
        System.out.println(bar.toString());

    }

}

测试应用程序的输出如下:

true
true
true
Baz@8aef2b
Baz@8aef2b
Bar@215bf054
Bar@215bf054

使用 3.0.6 它工作得非常好(单例 bean 确实是单例)。可能还有其他你没有在这里说明的东西弄乱了你的配置。当然,作为旁注,使用默认包可能会导致一些神秘的魔法发生;-)

于 2012-11-29T00:42:22.880 回答