Сколько раз Prototype Bean создается как зависимость Singleton Bean?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Prototype Bean как зависимость Singleton Bean
Prototype Bean — это bean, который создаётся КАЖДЫЙ РАЗ при запросе. Singleton Bean — это bean, который создаётся ОДИН РАЗ при инициализации контекста. Это создаёт интересную проблему, когда Prototype bean является зависимостью Singleton bean.
Ответ: Один раз при инициализации Singleton
Prototype bean создаётся ОДИН РАЗ — во время инициализации контекста Spring, когда создаётся Singleton bean. После этого тот же экземпляр Prototype bean используется постоянно вместе с Singleton bean.
Почему это происходит
Spring использует Dependency Injection (конструктор или сеттер). Когда Singleton bean инициализируется, Spring внедряет в него зависимость Prototype bean. Но это происходит один раз, при создании Singleton bean.
Пример проблемы
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class SingletonService {
private PrototypeService prototypeService;
// Constructor injection — это проблема!
public SingletonService(PrototypeService prototypeService) {
System.out.println("Singleton initialized with PrototypeService");
this.prototypeService = prototypeService;
}
public void doSomething() {
// Это ВСЕГДА один и тот же объект PrototypeService
System.out.println("Using: " + System.identityHashCode(prototypeService));
prototypeService.process();
}
}
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class PrototypeService {
public PrototypeService() {
System.out.println("PrototypeService created: " + System.identityHashCode(this));
}
public void process() {
System.out.println("Processing...");
}
}
Вывод:
PrototypeService created: 12345
Singleton initialized with PrototypeService
Using: 12345 (первый вызов)
Using: 12345 (второй вызов)
Using: 12345 (третий вызов — ЖЕ ТОТ ЖЕ ОБЪЕКТ!)
Проблема в том, что Prototype bean создан ОДИН раз и переиспользуется.
Решение 1: ObjectFactory (РЕКОМЕНДУЕТСЯ)
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class SingletonService {
private ObjectFactory<PrototypeService> prototypeFactory;
// Inject ObjectFactory вместо самого bean
public SingletonService(ObjectFactory<PrototypeService> prototypeFactory) {
this.prototypeFactory = prototypeFactory;
}
public void doSomething() {
// Каждый вызов создаст НОВЫЙ объект Prototype
PrototypeService prototype = prototypeFactory.getObject();
System.out.println("Using: " + System.identityHashCode(prototype));
prototype.process();
}
}
Вывод:
PrototypeService created: 12345 (первый вызов)
Using: 12345
PrototypeService created: 67890 (второй вызов)
Using: 67890
PrototypeService created: 54321 (третий вызов)
Using: 54321 (РАЗНЫЕ ОБЪЕКТЫ!)
Решение 2: Provider (Java стандарт)
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class SingletonService {
private Provider<PrototypeService> prototypeProvider;
// Inject Provider вместо bean
public SingletonService(Provider<PrototypeService> prototypeProvider) {
this.prototypeProvider = prototypeProvider;
}
public void doSomething() {
// get() создаёт новый объект каждый раз
PrototypeService prototype = prototypeProvider.get();
System.out.println("Using: " + System.identityHashCode(prototype));
prototype.process();
}
}
Зависимость:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
Решение 3: Method Injection (весьма редко)
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class SingletonService {
public void doSomething() {
// Каждый вызов создаёт новый объект
PrototypeService prototype = createPrototypeService();
System.out.println("Using: " + System.identityHashCode(prototype));
prototype.process();
}
@Lookup
protected PrototypeService createPrototypeService() {
// Spring переопределит этот метод
// Возвращает всегда новый Prototype bean
return null;
}
}
Решение 4: ApplicationContext (когда нужен контроль)
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class SingletonService {
private ApplicationContext applicationContext;
public SingletonService(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void doSomething() {
// Получить новый bean из контекста
PrototypeService prototype = applicationContext.getBean(PrototypeService.class);
System.out.println("Using: " + System.identityHashCode(prototype));
prototype.process();
}
}
Пример с реальным кейсом
// ТА ПРОБЛЕМА: Request-scoped bean в Singleton
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class UserContext {
private String currentUserId;
public void setCurrentUserId(String id) {
this.currentUserId = id;
}
public String getCurrentUserId() {
return currentUserId;
}
}
// БЕЗ ObjectFactory — ВСЕ ЗАПРОСЫ ИСПОЛЬЗУЮТ ОДНОГО ПОЛЬЗОВАТЕЛЯ!
@Service
public class OrderService {
private UserContext userContext; // Создан один раз при инициализации
public OrderService(UserContext userContext) {
this.userContext = userContext; // Это Request scope, но создан один раз!
}
public void processOrder() {
// Все запросы видят ID первого пользователя!
System.out.println("User: " + userContext.getCurrentUserId());
}
}
// С ObjectFactory — КАЖДЫЙ ЗАПРОС ПОЛУЧАЕТ СВОЙ CONTEXT
@Service
public class OrderService {
private ObjectFactory<UserContext> userContextFactory;
public OrderService(ObjectFactory<UserContext> userContextFactory) {
this.userContextFactory = userContextFactory;
}
public void processOrder() {
// Каждый запрос получает СВОЙ UserContext
UserContext userContext = userContextFactory.getObject();
System.out.println("User: " + userContext.getCurrentUserId());
}
}
Сравнение подходов
ObjectFactory (лучший выбор):
- Встроен в Spring
- Ленивая инициализация
- Типобезопасно
- Легко тестировать
Provider:
- Java стандарт
- Чуть более verbose
- Хороший выбор если нужна portability
@Lookup:
- Выглядит просто
- Требует CGLIB прокси
- Может быть медленнее
ApplicationContext:
- Мощный но неаккуратный
- Service Locator паттерн (антипаттерн)
- Сложнее тестировать
- Используй только если действительно нужен контроль
Проверка Scope
public class ScopeCheckExample {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// Проверить, что Prototype создаётся новый каждый раз
PrototypeService p1 = context.getBean(PrototypeService.class);
PrototypeService p2 = context.getBean(PrototypeService.class);
PrototypeService p3 = context.getBean(PrototypeService.class);
System.out.println("p1 equals p2: " + (p1 == p2)); // false
System.out.println("p2 equals p3: " + (p2 == p3)); // false
}
}
Вывод
Prototype bean создаётся ОДИН РАЗ при инициализации Singleton bean, если используется прямое внедрение через конструктор или сеттер. Это частая ошибка! Правильное решение — использовать ObjectFactory для получения нового экземпляра Prototype bean каждый раз при необходимости. Это очень важно для request-scoped и session-scoped beans. Это должен знать каждый Spring разработчик.