Весна создает несколько экземпляров синглтона?


У меня есть график весенних бобов, которые автоматически связывают друг друга. Сильно упрощенная иллюстрация:

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

Все эти бобы не имеют определенной области видимости, которая подразумевает, что они являются синглетонами (что делает их явными синглетонами, ничего не меняет, я пробовал).

Проблема заключается в том, что после создания экземпляра одного контекста приложения экземпляры Bar и Baz содержат различные экземпляры 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'

Я понимаю, что мои бобы перекрестно ссылаются друг на друга, но я ожидал бы, что Spring framework будет уважать синглетную область видимости и инициализировать синглетный Боб один раз, а затем автоматически подключать его к тот, кто этого хочет.

Интересный факт, что если я использую конструктор старой школы private с доступом public static Foo getInstance, это работает просто отлично - никакие исключения не выбрасываются во время установки контекста.

FWIW, я использую Spring версии 3.0.5 (также пробовал с 3.1.2, те же результаты) с o.s.c.s.ClassPathXmlApplicationContext(String ...configLocations) конструктором.

Я могу легко преобразовать свой код в статический инициализатор, но я хочу понять, почему Spring ведет себя таким образом. Это что, жучок?

EDIT: некоторые дополнительные исследование показало, что

  • после инициализации контекста приложения все последующие запросы к context.getBean(Foo.class) Всегда возвращает один и тот же экземпляр Foo.
  • замена @Autowired на сеттеры (около 20 использований этого боба) все еще приводит к множественным конструкциям этого объекта, но все зависимости вводятся стой же самой ссылкой.

Мне выше кажется, что это весенний баг, относящийся к реализации @Autowired. Я собираюсь опубликовать на весенние форумы сообщества и обратно сюда, если мне удастся получить что-нибудь полезное.

4 15

4 ответа:

Дочерний контекст(ы) может повторно создать те же одноэлементные бобы, если вы не будете осторожны с аннотациями context:component-scan (есть и другие аннотации Spring context scan, такие как MVC и другие). Это обычная проблема при использовании Spring сервлетов в веб-приложениях, см. Почему DispatcherServlet создает другой контекст приложения?

Убедитесь, что вы не повторяете сканирование компонентов в дочерних контекстах, или вы сканируете только определенные пакеты / аннотации и исключение указанных пакетов / аннотаций из проверки компонентов корневого контекста.

По какой-то причине мы получаем это выскакивание случайным образом в интеграционных тестах и сервисах (весенняя версия 4.1.4, java 1.8).

Похоже, что там может быть больше, чем один виновник - поначалу казалось, что причиной этого является Автопроводка.

Тем не менее, мы решили наиболее последовательные ошибки, гарантируя, что мы даем каждому затронутому Бобу поле "id".

Попробуйте использовать инъекцию setter вместо конструктора и посмотрите, если это так works.In в xml-файле spring bean указывается ссылка Bean A на Bean B и наоборот.

Моя весенняя конфигурация выглядела следующим образом:

<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 он работает отлично (синглетные бобы действительно синглетоны). Возможно, что-то еще, что вы не проиллюстрировали здесь, испортило вашу конфигурацию. Конечно, в качестве побочного замечания, использование пакета по умолчанию может привести к возникновению некой мистической магии; -)