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

Что будет лежать в контексте, если Scope первого бина - Singlenon, второго бина - Prototype в Spring проекте?

2.0 Middle🔥 131 комментариев
#Spring Framework

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

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

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

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 →SingletonPrototypeRequestSession
Прямая инъекция✅ OK❌ Проблема❌ Проблема❌ Проблема
ObjectFactory✅ OK✅ OK✅ OK✅ OK
@Lookup✅ OK✅ OK✅ OK✅ OK
Provider✅ OK✅ OK✅ OK✅ OK

Резюме

Что находится в контексте при Singleton + Prototype:

  1. Singleton бин — его экземпляр кешируется в контексте
  2. Prototype бин — только его BeanDefinition, экземпляры НЕ кешируются
  3. Инъекция — Singleton инжектирует ObjectFactory или Provider, чтобы получать новые Prototype экземпляры

Ключевой момент: Singleton не может напрямую зависеть от Prototype, иначе получит один экземпляр. Нужно использовать ObjectFactory, @Lookup или Provider для получения новых экземпляров на лету.

Что будет лежать в контексте, если Scope первого бина - Singlenon, второго бина - Prototype в Spring проекте? | PrepBro