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