Гарантирует ли Spring, что Singleton бин будет иметь ровно один экземпляр и можно ли это нарушить
Что проверяют
Знаете ли вы, что singleton в Spring это не синглтон из GoF: гарантия действует в рамках одного контейнера и одного определения бина, а не всей JVM. Следом обычно спрашивают, что будет с prototype-бином внутри singleton.
Как отвечать
Scope singleton стоит по умолчанию. Контейнер создаёт экземпляр один раз (при старте или при первом обращении, если стоит @Lazy), кладёт его в кеш и дальше отдаёт тот же объект. Создание синхронизировано в DefaultSingletonBeanRegistry, два потока не получат две копии.
Нарушить «ровно один» можно вполне законно:
- два определения одного класса: два метода
@Beanдают два бина одного типа. Единственность считается по имени бина, а не по классу; - несколько контекстов: родительский и дочерний
ApplicationContext. В тестах Spring кеширует контекст по конфигурации, и другой набор@MockitoBeanили профилей поднимает новый контекст со своими синглтонами; - lite-режим: при
@Configuration(proxyBeanMethods = false)или@Beanв обычном@Componentвызов одного@Bean-метода из другого это просто вызов Java, он создаёт объект в обход контейнера; - ручное создание:
new MyService()или рефлексия дают экземпляр, о котором контейнер не знает; - пересоздание контекста:
refresh(), рестарт DevTools,@RefreshScopeв Spring Cloud.
Уточните, что @Scope("prototype") не нарушение, а другая область видимости. А prototype-бин, внедрённый в singleton, создаётся один раз при внедрении; за свежим экземпляром ходят через ObjectProvider или @Lookup.
Где спотыкаются
Называют многопоточность способом нарушить единственность. Потокобезопасность это отдельная тема: экземпляр по-прежнему один, просто его состояние общее для всех потоков, поэтому синглтоны делают без изменяемых полей.
Думают, что singleton означает один экземпляр класса на приложение, и удивляются двум объектам одного типа при двух @Bean.
Не знают про proxyBeanMethods. В полном режиме класс @Configuration проксируется через CGLIB, и повторный вызов метода возвращает бин из контейнера, а без прокси это уже новая копия.