Вопрос 10 · Раздел 5

Что такое scope Bean?

НЕ храните изменяемое состояние в Singleton бинах (например, кэш пользователя в поле сервиса). Это приведёт к race condition и утечкам памяти. Вместо этого:

Версии по языкам: English Russian Ukrainian

🟢 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

  1. Singleton — по умолчанию (Stateless!)
  2. Prototype — редко. Если вам просто нужен новый объект без зависимостей Spring — используйте new. Но если объект требует внедрения зависимостей — Prototype оправдан.
  3. Request/Session — для web данных
  4. Scoped Proxy — для внедрения узких scope в широкие
  5. ObjectProvider — альтернатива Scoped Proxy. ObjectProvider vs ScopedProxy: ObjectProvider — явный вызов getObject() в коде (более читаемо). ScopedProxy — прокси прозрачно подставляется (менее заметно, но удобнее). ObjectProvider предпочтительнее для явного контроля.
  6. Избегайте состояния в 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]]
  • [[9. Что делают методы с аннотацией @PostConstruct и @PreDestroy]]
  • [[4. Что такое Bean в Spring]]
  • [[1. Что такое Dependency Injection]]