Являются ли Spring Singleton бины антипаттерном и почему
Что проверяют
Различаете ли вы паттерн Singleton из классического каталога и область видимости бина в Spring. Вопрос с подвохом: ждут не заученного «синглтон это зло», а понимания, какие свойства делают паттерн вредным и какие из них к спринговому бину отношения не имеют. Заодно проверяют, помните ли вы, что такой бин обслуживает все потоки сразу.
Как отвечать
Антипаттерном ругают классическую реализацию: приватный конструктор, статический getInstance(), глобальная точка доступа. Плохо в ней не количество экземпляров, а жёсткая статическая связь: класс сам решает, как его создавать, зависимость не видна в сигнатуре, подменить её в тесте нечем.
Spring singleton устроен иначе: контейнер создаёт один экземпляр на контекст и раздаёт его через внедрение зависимостей. Класс остаётся обычным, зависимости видны в конструкторе, в тесте подставляется заглушка. Глобальной точки доступа нет, поэтому сам по себе такой бин не антипаттерн.
Опасность одна и настоящая: состояние. Один экземпляр на контекст означает, что его поля разделяют все запросы, идущие параллельно.
@Service
class ReportService {
private final ReportRepository repository; // ок, неизменяемая зависимость
private int processed; // гонка: поле общее на все потоки
}Уместно добавить, что экземпляр один на контейнер, а не на JVM, и создаётся при старте контекста.
Где спотыкаются
Отвечают «да, антипаттерн, глобальное состояние и не тестируется», не заметив, что у спрингового бина нет ни статического доступа, ни самостоятельного создания: он подменяется заглушкой одной строкой.
Кладут в singleton изменяемое поле: счётчик, накопленный список, непотокобезопасный форматтер дат. Баг всплывает только под нагрузкой и плавает. Лечат его synchronized на методе и получают узкое место вместо того, чтобы вынести состояние наружу.
Предлагают как лекарство prototype, хотя тот решает другую задачу. Внедрённый полем prototype-бин достанется singleton один раз при создании и застрянет в нём навсегда, для честного поведения нужен ObjectProvider или прокси области видимости. К тому же destroy-методы у prototype контейнер не вызывает.