← Назад к вопросам

Сколько раз Prototype Bean создается как зависимость Singleton Bean?

1.3 Junior🔥 251 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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 разработчик.