Що таке Bean scope?
НЕ зберігайте змінюваний стан у Singleton бінах (наприклад, кеш користувача в полі сервісу). Це призведе до race condition і витоків пам'яті. Замість цього:
🟢 Junior Level
Scope — визначає, скільки екземплярів біна створює Spring.
Основні scope:
| Scope | Скільки екземплярів |
|---|---|
| Singleton | Один на весь додаток (за замовчуванням) |
| Prototype | Новий при кожному запиті |
@Service // Singleton за замовчуванням
public class UserService { }
@Scope("prototype")
@Service
public class ReportGenerator { }
Приклад:
// Singleton: завжди один і той самий
UserService s1 = context.getBean(UserService.class);
UserService s2 = context.getBean(UserService.class);
s1 == s2 // → true
// Prototype: щоразу новий
ReportGenerator r1 = context.getBean(ReportGenerator.class);
ReportGenerator r2 = context.getBean(ReportGenerator.class);
r1 == r2 // → false
🟡 Middle Level
Singleton
// За замовчуванням!
@Service
public class UserService { }
// Один екземпляр на ApplicationContext
// Усі потоки використовують один об'єкт
→ Повинен бути Stateless (без стану)!
Prototype
@Scope("prototype")
@Service
public class ReportGenerator { }
// Новий екземпляр при КОЖНОМУ getBean()
// Spring створює, але НЕ управляє знищенням!
→ @PreDestroy НЕ викликається
Web Scopes
@Scope("request") // Один екземпляр на HTTP запит
@Scope("session") // Один екземпляр на HTTP сесію
@Scope("application") // Один на ServletContext
Проблема Prototype в Singleton
// ❌ Prototype створиться ОДИН раз!
@Service
public class SingletonService {
@Autowired
private PrototypeBean prototype; // Застряв один екземпляр!
}
// ✅ ObjectProvider
@Service
public class SingletonService {
@Autowired
private ObjectProvider<PrototypeBean> provider;
public void process() {
PrototypeBean bean = provider.getObject(); // Новий щоразу!
}
}
🔴 Senior Level
Коли НЕ використовувати Singleton зі змінюваним станом
НЕ зберігайте змінюваний стан у Singleton бінах (наприклад, кеш користувача в полі сервісу). Це призведе до race condition і витоків пам’яті. Замість цього:
- Винесіть стан у зовнішнє сховище (Redis, БД)
- Використовуйте Request scope для даних запиту
- Використовуйте Prototype якщо потрібен новий екземпляр
Scoped Proxy
// Вирішення проблеми Prototype/Request в Singleton
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPreferences preferences() {
return new UserPreferences();
}
// Spring впровадить проксі в Singleton
// Проксі при кожному виклику звертається до RequestContextHolder (ThreadLocal), який зберігає атрибути поточного HTTP-запиту. Таким чином, навіть singleton-бін, що інжектує request-scoped бін, щоразу отримує актуальний об'єкт для поточного запиту.
Under the hood: де зберігаються біни
Singleton → DefaultSingletonBeanRegistry (ConcurrentHashMap)
Request → RequestContextHolder (ThreadLocal) — ScopedProxyMode.TARGET_CLASS створює CGLIB-проксі, який при кожному виклику звертається до актуальної області (request/session). RequestContextHolder зберігає дані поточного HTTP-запиту в ThreadLocal.
Session → SessionAttributes
Prototype → Створюється, не зберігається
Custom Scope
// Свій scope!
public class ThreadScope implements Scope {
private final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();
public Object get(String name, ObjectFactory<?> factory) {
return threadLocal.get().computeIfAbsent(name, k -> factory.getObject());
}
}
// Реєстрація
context.getBeanFactory().registerScope("thread", new ThreadScope());
Production Experience
Реальний сценарій: Request бін в Singleton
// ❌ Singleton отримав Request бін під час старту
// → Усі запити використовують дані першого користувача!
// ✅ ScopedProxyMode.TARGET_CLASS
// → Проксі → RequestContextHolder → актуальний бін
Best Practices
- Singleton — за замовчуванням (Stateless!)
- Prototype — рідко. Якщо вам просто потрібен новий об’єкт без залежностей Spring — використовуйте
new. Але якщо об’єкт вимагає впровадження залежностей — Prototype виправданий. - Request/Session — для web даних
- Scoped Proxy — для впровадження вузьких scope в широкі
- ObjectProvider — альтернатива Scoped Proxy. ObjectProvider vs ScopedProxy: ObjectProvider — явний виклик
getObject()в коді (більш читабельно). ScopedProxy — проксі прозоро підставляється (менш помітно, але зручніше). ObjectProvider переважніший для явного контролю. - Уникайте стану в Singleton
Резюме для Senior
- Singleton = один на додаток, Stateless
- Prototype = новий щоразу, @PreDestroy НЕ працює
- Request/Session = web scope
- Scoped Proxy = проксі → RequestContextHolder
- ObjectProvider = явний запит біна
- Custom Scope = свій lifecycle
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- Singleton (за замовчуванням) — один екземпляр на ApplicationContext, повинен бути Stateless
- Prototype — новий екземпляр при кожному запиті,
@PreDestroyНЕ викликається, Spring не управляє знищенням - Web scopes:
request(на HTTP-запит),session(на HTTP-сесію),application(на ServletContext) - Проблема Prototype в Singleton: prototype створюється ОДИН раз при впровадженні — використовуйте
ObjectProvider.getObject() - Scoped Proxy (
ScopedProxyMode.TARGET_CLASS) — CGLIB-проксі звертається доRequestContextHolder(ThreadLocal) при кожному виклику - Уникайте змінюваного стану в Singleton — race condition і витоки пам’яті
- Custom Scope — можна створити свій (наприклад,
ThreadScopeчерезThreadLocal) - Singleton зберігає біни в
DefaultSingletonBeanRegistry(ConcurrentHashMap), Request — вThreadLocal
Часті уточнюючі запитання:
- Як впровадити Prototype в Singleton?
ObjectProvider<PrototypeBean>.getObject()— новий щоразу, або Scoped Proxy. - Що таке Scoped Proxy? Проксі, яке при кожному виклику звертається до актуальної області (request/session) через
RequestContextHolder. - Чому Singleton повинен бути Stateless? Один об’єкт використовується всіма потоками — змінюваний стан = race condition.
- Коли використовувати Prototype замість
new? Коли об’єкт вимагає впровадження залежностей від Spring.
Червоні прапорці (НЕ говорити):
- «Singleton можна зберігати змінюваний стан» (race condition і витоки пам’яті)
- «Prototype біни знищуються автоматично» (
@PreDestroyНЕ викликається, чистіть вручну) - «Request/Session scope працюють без Scoped Proxy в Singleton» (буде один екземпляр на всі запити!)
- «ObjectProvider і Scoped Proxy — одне й те саме» (ObjectProvider — явний
getObject(), ScopedProxy — прозора підстановка)
Пов’язані теми:
- [[6. Що таке Bean Lifecycle]]
- [[7. Які етапи є у Bean lifecycle]]
- [[9. Що роблять методи з анотаціями @PostConstruct та @PreDestroy]]
- [[4. Що таке Bean в Spring]]
- [[1. Що таке Dependency Injection]]