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

Контролирует ли Spring Scope Prototype

2.2 Middle🔥 171 комментариев
#Spring Framework

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

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

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

# Spring Bean Scope Prototype: Как Spring контролирует время жизни

Это отличный вопрос, раскрывающий глубокое понимание Spring контейнера. Ответ неочевидный: Spring НЕ контролирует полный жизненный цикл Prototype бина, только его создание.

Основные Spring Scopes

1. Singleton (по умолчанию)

@Bean // или @Service, @Component по умолчанию
public UserService userService() {
    return new UserService();
}

// Spring контролирует:
// - Создание (один раз при старте)
// - Инициализацию (вызов @PostConstruct)
// - Кэширование (тот же экземпляр всегда)
// - Уничтожение (вызов @PreDestroy при shutdown)

// Время жизни: От создания до shutdown приложения

2. Prototype (ключевой момент)

@Component
@Scope("prototype")
public class RequestContext {
    
    @PostConstruct
    public void init() {
        System.out.println("RequestContext инициализирован");
    }
    
    @PreDestroy
    public void destroy() {
        System.out.println("RequestContext уничтожен");
    }
}

// Spring контролирует:
// ✅ Создание (каждый раз новый экземпляр)
// ✅ Инициализацию (вызов @PostConstruct)
// ❌ Уничтожение (NOT контролирует @PreDestroy!)
// ❌ Кэширование (не кэширует)

// Время жизни: От создания до... ??? (Spring не контролирует)

Ключевое различие: Singleton vs Prototype

// SINGLETON
@Component
@Scope("singleton") // или просто @Component (default)
public class SingletonService {
    @PostConstruct
    public void init() {
        System.out.println("SingletonService создан");
    }
    
    @PreDestroy
    public void cleanup() {
        System.out.println("SingletonService удалён");
    }
}

// PROTOTYPE
@Component
@Scope("prototype")
public class PrototypeService {
    @PostConstruct
    public void init() {
        System.out.println("PrototypeService создан");
    }
    
    @PreDestroy
    public void cleanup() {
        System.out.println("PrototypeService удалён"); // ❌ НЕ будет вызван!
    }
}

// Тест
@SpringBootTest
public class ScopeTest {
    @Autowired
    private ApplicationContext context;
    
    @Test
    public void testSingletonLifecycle() {
        SingletonService s1 = context.getBean(SingletonService.class);
        SingletonService s2 = context.getBean(SingletonService.class);
        
        // Output: "SingletonService создан" (один раз!)
        System.out.println(s1 == s2); // true (один и тот же объект)
        
        // При shutdown приложения:
        // Output: "SingletonService удалён"
    }
    
    @Test
    public void testPrototypeLifecycle() {
        PrototypeService p1 = context.getBean(PrototypeService.class);
        PrototypeService p2 = context.getBean(PrototypeService.class);
        
        // Output:
        // "PrototypeService создан"
        // "PrototypeService создан" (два раза!)
        
        System.out.println(p1 == p2); // false (разные объекты)
        
        // При shutdown приложения:
        // Output: (ничего - @PreDestroy не вызывается!)
        // p1 и p2 остаются в памяти (memory leak потенциальный)
    }
}

Почему Spring НЕ контролирует Prototype @PreDestroy?

Это по дизайну:

// Spring выдаёт вам Prototype бин
PrototypeService prototype1 = context.getBean(PrototypeService.class);
PrototypeService prototype2 = context.getBean(PrototypeService.class);
PrototypeService prototype3 = context.getBean(PrototypeService.class);
// ... миллиард других бинов

// Как Spring узнает, когда удалить прототип?
// Может быть, вы ещё используете prototype1?
// Может быть, prototype2 передали в другой объект?
// Может быть, prototype3 хранится в какой-то коллекции?

// Spring не может следить за всеми ссылками
// Это работа Garbage Collector!

// Когда на бин нет ссылок → GC удаляет объект
// Если бин имеет @PreDestroy, JVM вызовет его только при финализации
// Но это ненадёжно (финализация может быть отложена или пропущена)

Явное управление жизненным циклом Prototype

Если нужно гарантировать cleanup, используйте это:

1. ObjectFactory

@Service
public class RequestProcessor {
    @Autowired
    private ObjectFactory<RequestContext> contextFactory;
    
    public void processRequest(String data) {
        // Создаём новый бин для каждого запроса
        RequestContext context = contextFactory.getObject();
        
        try {
            context.process(data);
        } finally {
            // Явно вызываем cleanup
            context.cleanup();
        }
    }
}

// RequestContext
@Component
@Scope("prototype")
public class RequestContext {
    
    @PostConstruct
    public void init() {
        System.out.println("Context создан");
    }
    
    public void process(String data) {
        System.out.println("Обработка: " + data);
    }
    
    public void cleanup() {
        System.out.println("Cleanup вызван явно");
    }
}

2. ObjectProvider (более гибкий)

@Service
public class DataProcessor {
    @Autowired
    private ObjectProvider<ProcessingContext> contextProvider;
    
    public void process(List<String> items) {
        for (String item : items) {
            // Получаем новый контекст для каждого элемента
            ProcessingContext context = contextProvider.getObject();
            
            try {
                doProcess(item, context);
            } finally {
                if (context instanceof AutoCloseable) {
                    ((AutoCloseable) context).close();
                }
            }
        }
    }
    
    private void doProcess(String item, ProcessingContext context) {
        context.handle(item);
    }
}

3. DisposableBean интерфейс

@Component
@Scope("prototype")
public class PrototypeResource implements DisposableBean {
    private Connection connection;
    
    @PostConstruct
    public void init() {
        this.connection = DatabaseUtil.getConnection();
        System.out.println("Соединение установлено");
    }
    
    @Override
    public void destroy() throws Exception {
        // Когда бин удаляется, закрываем ресурсы
        if (connection != null) {
            connection.close();
            System.out.println("Соединение закрыто");
        }
    }
}

Другие Scopes в Spring

// 1. REQUEST (для веб приложений)
@Component
@Scope("request")
public class RequestData {
    // Новый экземпляр для каждого HTTP запроса
    // Spring управляет жизненным циклом
}

// 2. SESSION (для веб приложений)
@Component
@Scope("session")
public class UserSession {
    // Один экземпляр на пользователя/сессию
    // Spring управляет жизненным циклом
}

// 3. APPLICATION (для веб приложений)
@Component
@Scope("application")
public class ApplicationConfig {
    // Один экземпляр на приложение (как singleton)
    // Но для ServletContext
}

// 4. WEBSOCKET (для WebSocket приложений)
@Component
@Scope("websocket")
public class WebSocketMessage {
    // Один экземпляр на WebSocket сессию
}

Пример: Потенциальная проблема с Prototype

// ❌ ПРОБЛЕМА: Singleton содержит ссылку на Prototype
@Component
public class SingletonService {
    @Autowired
    private PrototypeService prototype;
    
    public void doWork() {
        // ❌ prototype всегда один и тот же объект!
        // Scope="prototype" игнорируется
        prototype.process();
    }
}

// ✅ РЕШЕНИЕ: Используйте ObjectFactory
@Component
public class SingletonService {
    @Autowired
    private ObjectFactory<PrototypeService> prototypeFactory;
    
    public void doWork() {
        // ✅ Каждый раз новый PrototypeService
        PrototypeService proto = prototypeFactory.getObject();
        proto.process();
    }
}

// ✅ АЛЬТЕРНАТИВА: Используйте @Lookup
@Component
public class SingletonService {
    
    @Lookup
    protected PrototypeService getPrototype() {
        return null; // Spring переопределит этот метод
    }
    
    public void doWork() {
        // ✅ Каждый раз новый PrototypeService
        PrototypeService proto = getPrototype();
        proto.process();
    }
}

Таблица управления жизненным циклом

ScopeСоздание@PostConstruct@PreDestroyКэширование
SingletonОдин раз (старт)✅ Вызывается✅ Вызывается✅ Да
PrototypeКаждый раз✅ Вызывается❌ НЕ вызывается❌ Нет
RequestНа запрос✅ Вызывается✅ ВызываетсяТолько в запросе
SessionНа сессию✅ Вызывается✅ ВызываетсяТолько в сессии

Вывод

Ответ на вопрос: "Контролирует ли Spring Scope Prototype?"

Частично:

Spring контролирует:

  • Создание новых экземпляров
  • Вызов @PostConstruct
  • Внедрение зависимостей

Spring НЕ контролирует:

  • Удаление объектов (@PreDestroy не вызывается)
  • Время жизни (это работа GC)
  • Cleanup ресурсов

Поэтому с Prototype бинами нужно быть осторожным:

  • Используйте ObjectFactory для явного управления жизненным циклом
  • Реализуйте DisposableBean для очистки ресурсов
  • Помните про потенциальные memory leaks

Rule of thumb: Используйте Prototype редко. В большинстве случаев достаточно Singleton с внедрёнными зависимостями.