【问题标题】:Spring creating multiple instances of a singleton?Spring创建单例的多个实例?
【发布时间】:2012-07-17 20:02:12
【问题描述】:

我有一个相互自动装配的 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不同 实例。怎么会这样?

我已尝试为Foo 创建公共无参数构造函数,并且调试已确认Foo 已多次创建。所有这些创建的堆栈跟踪都是here

我还尝试为 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实例。
  • 用 setter 替换 @Autowired(这个 bean 大约有 20 次使用)仍然会导致这个对象的多个构造,但所有依赖项都注入了相同的引用。

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

【问题讨论】:

  • 这可能很明显,但只有 1 个 JVM 在运行吗?循环依赖?
  • 是的,这只是一个JVM。循环依赖 - 是的,但我相信我在我的帖子中解释了这一点。
  • 我明白了,但是如果你有例如构造函数注入会发生什么? Spring 应该如何解决这个问题?
  • 构造和连接给定对象不是单个原子,而是两个不同的操作。在我的示例中,容器可以先实例化所有 bean,然后再设置 @Autowired 依赖项。或者也许我没有明白你的意思 - 如果你有任何特殊情况,请分享。
  • 我想您的示例过于简单,无法调查问题。 Stacktrace 显示涉及到一些FactoryBean,以及FactoryBeans may cause problems with circular references

标签: java spring singleton autowired applicationcontext


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 这些单例会加载相同的类加载器吗?
    • 你能提供最小的例子,弹簧单例将加载两次吗?
    【解决方案3】:

    我的 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 确实是单例)。可能还有其他你没有在这里说明的东西弄乱了你的配置。当然,作为旁注,使用默认包可能会导致一些神秘的魔法发生;-)

    【讨论】:

    • 感谢您为此付出的努力。在我的情况下,它是更复杂的对象图,有数百个。由于显而易见的原因,我无法在此处发布所有这些内容,只需剪切最小的场景来说明这一点。
    • @mindas 您是否尝试在文件中重新排序 bean 定义?尝试将 Foo 放在第二个或最后一个位置。
    【解决方案4】:

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

    【讨论】:

    • 我更新了我的帖子。重申一下 - 我知道如何解决问题,但更重要的是我试图了解为什么会发生这种情况。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多