Что будет лежать в контексте, если Scope первого бина - Singlenon, второго бина - Prototype в Spring проекте?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Spring Scopes: Singleton vs Prototype и их взаимодействие в контексте
В Spring контексте могут храниться бины разных scopes. Взаимодействие между ними создаёт важные нюансы в управлении жизненным циклом.
Что такое Scope?
Scope определяет, как долго живёт экземпляр бина и когда создаются новые экземпляры.
Singleton Scope
Singleton — один единственный экземпляр бина на весь ApplicationContext.
@Configuration
public class AppConfig {
@Bean
@Scope("singleton") // или просто @Bean (по умолчанию)
public ServiceA serviceA() {
return new ServiceA();
}
}
// Что находится в контексте:
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ServiceA service1 = context.getBean(ServiceA.class);
ServiceA service2 = context.getBean(ServiceA.class);
assert service1 == service2; // true! Один и тот же объект
System.out.println(service1 == service2); // true
}
В контексте хранится: один объект ServiceA, который переиспользуется при каждом запросе getBean().
Prototype Scope
Prototype — каждый раз при запросе бина создаётся новый экземпляр.
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public ServiceB serviceB() {
return new ServiceB();
}
}
// Что находится в контексте:
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ServiceB service1 = context.getBean(ServiceB.class);
ServiceB service2 = context.getBean(ServiceB.class);
assert service1 != service2; // true! Разные объекты
System.out.println(service1 == service2); // false
}
В контексте НЕ хранится: контекст создаёт новый объект при каждом вызове, но не кеширует его. Ответственность за управление жизненным циклом переходит на клиента.
Когда Singleton зависит от Prototype: Проблема
Проблемный сценарий:
@Component
@Scope("singleton")
public class SingletonService {
private final PrototypeService prototypeService;
public SingletonService(PrototypeService prototypeService) {
this.prototypeService = prototypeService; // Инжектируется один раз!
}
public void doSomething() {
System.out.println("Using: " + prototypeService);
// Каждый раз используется ОДИН И ТОТ ЖЕ prototypeService!
}
}
@Component
@Scope("prototype")
public class PrototypeService {
// ...
}
// Что находится в контексте:
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(/*config*/);
SingletonService service1 = context.getBean(SingletonService.class);
SingletonService service2 = context.getBean(SingletonService.class);
assert service1 == service2; // true — singleton
// НО:
service1.doSomething(); // Using: PrototypeService@12345
service2.doSomething(); // Using: PrototypeService@12345 (ОДИН ТОТ ЖЕ!)
// PrototypeService переиспользуется, хотя должен быть новый!
}
Почему это проблема?
- PrototypeService создаётся один раз при инъекции в SingletonService constructor
- Состояние в PrototypeService может накапливаться
- Если несколько потоков используют SingletonService, они получают один PrototypeService
Решение 1: ObjectFactory
@Component
@Scope("singleton")
public class SingletonService {
private final ObjectFactory<PrototypeService> prototypeFactory;
public SingletonService(ObjectFactory<PrototypeService> prototypeFactory) {
this.prototypeFactory = prototypeFactory;
}
public void doSomething() {
PrototypeService service = prototypeFactory.getObject(); // Новый экземпляр!
System.out.println("Using: " + service);
}
}
// Что находится в контексте:
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(/*config*/);
SingletonService service = context.getBean(SingletonService.class);
service.doSomething(); // Using: PrototypeService@12345
service.doSomething(); // Using: PrototypeService@67890 (новый!)
}
Решение 2: Lookup Method Injection
@Component
@Scope("singleton")
public class SingletonService {
@Lookup // Spring переопределит этот метод через CGLIB прокси
public PrototypeService getPrototypeService() {
return null; // Implementation будет сгенерирована
}
public void doSomething() {
PrototypeService service = getPrototypeService(); // Новый экземпляр!
System.out.println("Using: " + service);
}
}
Решение 3: Provider интерфейс (JSR-330)
import javax.inject.Provider;
@Component
@Scope("singleton")
public class SingletonService {
private final Provider<PrototypeService> prototypeProvider;
public SingletonService(Provider<PrototypeService> prototypeProvider) {
this.prototypeProvider = prototypeProvider;
}
public void doSomething() {
PrototypeService service = prototypeProvider.get(); // Новый экземпляр!
System.out.println("Using: " + service);
}
}
Что находится в контексте: подробный анализ
ApplicationContext содержит:
public class SpringContextExample {
@Bean
@Scope("singleton")
public SingletonService singletonService(
ObjectFactory<PrototypeService> prototypeFactory) {
return new SingletonService(prototypeFactory);
}
@Bean
@Scope("prototype")
public PrototypeService prototypeService() {
return new PrototypeService();
}
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(
SpringContextExample.class
);
// В контексте находится:
// 1. SingletonService — один экземпляр (закеширован в DefaultListableBeanFactory)
SingletonService singleton = context.getBean("singletonService", SingletonService.class);
// В контексте кешируется
// 2. PrototypeService — BeanDefinition, но экземпляры НЕ кешируются
// Каждый вызов getBean("prototypeService") создаёт новый
PrototypeService proto1 = context.getBean("prototypeService", PrototypeService.class);
PrototypeService proto2 = context.getBean("prototypeService", PrototypeService.class);
assert proto1 != proto2; // true
// 3. ObjectFactory<PrototypeService> в singleton'е — это прокси
// Каждый вызов getObject() создаёт новый PrototypeService
}
}
Другие Scopes в Web-приложениях
// request scope — новый для каждого HTTP запроса
@Component
@Scope("request")
public class RequestScopedService { }
// session scope — один для сессии пользователя
@Component
@Scope("session")
public class SessionScopedService { }
// application scope — один на всё приложение (как singleton, но для WebApplicationContext)
@Component
@Scope("application")
public class ApplicationScopedService { }
Проблема Singleton + Request scope:
@Component
@Scope("singleton")
public class GlobalService {
private final RequestScopedService requestService; // Проблема!
public GlobalService(RequestScopedService requestService) {
this.requestService = requestService; // Инжектируется один раз
}
}
// Решение: использовать ObjectFactory или @Lookup
@Component
@Scope("singleton")
public class GlobalService {
private final ObjectFactory<RequestScopedService> requestServiceFactory;
public GlobalService(ObjectFactory<RequestScopedService> factory) {
this.requestServiceFactory = factory;
}
public void processRequest() {
RequestScopedService service = requestServiceFactory.getObject(); // Новый для каждого запроса
}
}
Таблица взаимодействия Scopes
| Singleton → | Singleton | Prototype | Request | Session |
|---|---|---|---|---|
| Прямая инъекция | ✅ OK | ❌ Проблема | ❌ Проблема | ❌ Проблема |
| ObjectFactory | ✅ OK | ✅ OK | ✅ OK | ✅ OK |
| @Lookup | ✅ OK | ✅ OK | ✅ OK | ✅ OK |
| Provider | ✅ OK | ✅ OK | ✅ OK | ✅ OK |
Резюме
Что находится в контексте при Singleton + Prototype:
- Singleton бин — его экземпляр кешируется в контексте
- Prototype бин — только его BeanDefinition, экземпляры НЕ кешируются
- Инъекция — Singleton инжектирует ObjectFactory или Provider, чтобы получать новые Prototype экземпляры
Ключевой момент: Singleton не может напрямую зависеть от Prototype, иначе получит один экземпляр. Нужно использовать ObjectFactory, @Lookup или Provider для получения новых экземпляров на лету.