Spring создание нескольких экземпляров одноэлементного?
У меня есть график Spring beans, который автоматически связывает друг с другом. Тяжелая упрощенная иллюстрация:
<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;
}
Все эти beans не имеют определенной области, которая подразумевает, что они являются одиночными (что делает их явные синглтоны ничего не меняет, я пробовал).
Проблема заключается в том, что после создания одного контекста приложения экземпляры Bar
и Baz
содержат разные экземпляры Foo
. Как это могло случиться?
Я попытался создать public no args constructor для 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'
Я понимаю, что мои beans перекрестно ссылаются друг на друга, но я ожидал бы, что структура Spring будет уважать одноэлементную область и инициализировать singleton bean один раз, а затем автоматически отнести ее к тому, кто ее хочет.
Интересный факт: если я использую конструктор старой школы private
с public static Foo getInstance
accessor, это работает просто отлично - во время настройки контекста не выбрасываются исключения.
FWIW, я использую Spring версию 3.0.5 (также пробовал с 3.1.2, те же результаты) с конструктором o.s.c.s.ClassPathXmlApplicationContext(String ...configLocations)
.
Я могу легко преобразовать свой код, чтобы использовать статический инициализатор, но я хочу понять, почему Spring ведет себя так. Это ошибка?
EDIT:. Некоторые дополнительные исследования показали, что
- После инициализации контекста приложения все последующие запросы
context.getBean(Foo.class)
всегда возвращают один и тот же экземпляр Foo
.
- Замена
@Autowired
на сеттеры (около 20 применений этого bean) все еще приводит к нескольким конструкциям этого объекта, но все зависимости вводятся с помощью той же ссылки.
Как мне кажется выше, это ошибка Spring, относящаяся к реализации @Autowired
. Я собираюсь публиковать сообщения на форумах сообщества Spring и возвращаться сюда, если мне удастся получить что-нибудь полезное.
Ответы
Ответ 1
Контекст ребенка может восстановить один и тот же singleton beans, если вы не будете осторожны с контекстом: аннотации компонентного сканирования (есть и другие аннотации контекстного сканирования Spring, такие как MVC и другие). Это обычная проблема при использовании сервлетов Spring в веб-приложениях, см. Почему DispatcherServlet создает другой контекст приложения?
Убедитесь, что вы не пересканируете свои компоненты в дочерних контекстах, или вы сканируете только определенные пакеты/аннотации и исключаете указанные пакеты/аннотации из сканирования корневого контекстного компонента.
Ответ 2
По какой-то причине мы получаем случайное всплытие в тестах интеграции и сервисах (spring версия 4.1.4, java 1.8).
Похоже, что может быть более одного преступника. Поначалу Autowiring вызывал это.
Тем не менее, мы разрешили самые последовательные сбои, гарантируя, что мы даем каждое воздействие на bean поле 'id'.
Ответ 3
Попробуйте использовать инсталляцию установщика вместо пути конструктора и посмотрите, работает ли он. В spring bean xml укажите bean A ref на bean B и наоборот.
Ответ 4
Моя конфигурация 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
[email protected]
[email protected]
[email protected]
[email protected]
Используя 3.0.6, он отлично работает (синглтон beans действительно является одиночным). Возможно, что-то еще вы не проиллюстрировали здесь, испортив вашу конфигурацию. Конечно, в качестве побочного примечания использование пакета по умолчанию может привести к возникновению некоторой магической магии: -)