16

I have a graph of Spring beans which autowire each other. Heavily simplified illustration:

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

All of these beans don't have scope specified which imply they are singletons (making them explicit singletons doesn't change anything, I've tried).

The problem is that after the instantiation of a single application context, instances of Bar and Baz contain different instances of Foo. How could this happen?

I have tried to create public no args constructor for Foo and debugging has confirmed Foo is created more than once. The stack trace for all of these creations is here.

I have also tried to enable debug logging for Spring, and among all other lines, got the following:

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'

I understand that my beans are cross-referencing each other, but I would expect Spring framework to respect singleton scope and initialize a singleton bean once, and then autowire it to whoever wants it.

The interesting fact that if I use old school private constructor with public static Foo getInstance accessor, this works just fine - no exceptions are thrown during the context setup.

FWIW, I am using Spring version 3.0.5 (also tried with 3.1.2, same results) with o.s.c.s.ClassPathXmlApplicationContext(String ...configLocations) constructor.

I can easily convert my code to use static initializer but I want to understand why would Spring behave this way. Is this a bug?

EDIT: Some additional investigation showed that

  • After the application context is initialized, all subsequent requests to context.getBean(Foo.class) always return the same instance of Foo.
  • Replacing @Autowired with setters (about 20 usages of this bean) still results multiple constructions of this object, but all dependencies are injected with the same reference.

To me above suggests that this is a Spring bug pertaining to @Autowired implementation. I am going to post to Spring community forums and post back here if I manage to obtain anything useful.

mindas
  • 26,463
  • 15
  • 97
  • 154
  • It may be obvious but is there only 1 JVM in play? Circular dependencies? – Adam Arold Jul 18 '12 at 17:49
  • Yes, this is only one JVM. Circular dependencies - yes, but I believe I explained this in my post. – mindas Jul 18 '12 at 17:50
  • I see but what happens if you have for example a constructor injection? How does Spring supposed to resolve that problem? – Adam Arold Jul 18 '12 at 17:51
  • Constructing and wiring given object is not a single atomic, but two different operations. In my example the container could instantiate all beans first and then set the @Autowired dependencies afterwards. Or maybe I didn't get your point - if you have any particular case in mind, please share. – mindas Jul 18 '12 at 17:55
  • Did you try debugging with Spring source code attached? It often solves the problem if you know what's going on under the hood. – Adam Arold Jul 18 '12 at 18:00
  • Yes, and I provided a link to the stack trace - see my question. If you can see any hints, don't hesitate to speak :) – mindas Jul 18 '12 at 18:02
  • Oh, sorry, I'll check that out. – Adam Arold Jul 18 '12 at 18:06
  • Strange, I am not able to replicate your behavior, I get the exact same instance of Foo in the Bar, Baz and the one injected into my test class.. – Biju Kunjummen Jul 18 '12 at 18:07
  • 4
    I guess your example is too much simplfied to investigate the problem. Stacktrace shows that some `FactoryBean` is involved, and `FactoryBean`s [may cause problems with circular references](https://jira.springsource.org/browse/SPR-6896). – axtavt Jul 18 '12 at 18:08
  • Well, I had to simplify this, the other option would be for me to send the entire project which is 100Ks of LOC. @axtavt - thanks for the hint, I'll investigate it. – mindas Jul 18 '12 at 18:13
  • Why do you need circular dependencies by the way? – Adam Arold Jul 18 '12 at 19:00
  • Do you have any child context(s)? This can happen for example in web application where Spring servlets will instantiate child context, and potentially re-instantiate beans. – rootkit Feb 11 '13 at 20:27
  • @rootkit007 yes, I have child contexts. Is there any reference I can look at in order to find out more about the strange behaviour of re-instantiation? – mindas Feb 12 '13 at 11:21

4 Answers4

13

Child context(s) can reinstantiate the same singleton beans if you are not careful with context:component-scan annotations (there are other Spring context scan annotations as well such as MVC ones and others). This is a common problem when using Spring servlets in web applications, see Why DispatcherServlet creates another application context?

Make sure you are not re-scanning your components in child contexts, or you are scanning only specific packages/annotations and excluding said packages/annotations from root context component scan.

Community
  • 1
  • 1
rootkit
  • 2,165
  • 2
  • 29
  • 43
1

For some reason we are getting this popping up randomly in integration tests and services as well (spring version 4.1.4, java 1.8).

Looks like there might be more than one culprit - Autowiring appeared to be causing this at first.

However, we have resolved the most consistent failures by ensuring we give each impacted bean an 'id' field.

Andy
  • 3,596
  • 8
  • 34
  • 33
0

Try using setter injection instead of constructor way and see if it works.In the spring bean xml specify Bean A ref to Bean B and vice versa.

Giri
  • 235
  • 4
  • 14
  • I have updated my post. Just to reiterate - I know how to fix the problem, but more importantly I am trying to understand **why** this happens. – mindas Jul 19 '12 at 10:26
0

My Spring configuration was like follows:

<context:annotation-config/>

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

Classes are identical to yours

Test app like follows:

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());

    }

}

Output from test app like follows:

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

Using 3.0.6 it works perfectly fine (singleton beans are indeed singletons). There might be something else you did not illustrate here messing up your configuration. Of course, as a side note, using default package may cause some misterious magic to happen ;-)

Michal Pasinski
  • 568
  • 7
  • 20
  • Thanks for putting effort into this. In my case it was much more complex graph of objects, hundreds of them. For obvious reasons I couldn't post all of them here, just cut the minimal scenario to illustrate the point. – mindas Nov 29 '12 at 09:24
  • @mindas Did you try to reorder beans definition in file? Try to put the Foo on the second, or last place. – partlov Dec 28 '12 at 08:13