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

Почему не вызывается метод destroy у Bean со scope="prototype" в Spring?

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

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

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

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

Почему не вызывается destroy() у Bean со scope="prototype"?

Это фундаментальное различие в управлении жизненным циклом Bean'ов между разными scopes в Spring. Причина кроется в разных моделях владения и ответственности за управление ресурсами.

Главная причина: ответственность за управление жизненным циклом

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

@Bean
public UserService userService() {  // scope = singleton
    return new UserService();
}

Spring контейнер:

  • Создаёт Bean один раз
  • Управляет его жизненным циклом полностью
  • Вызывает @PostConstruct, init-method
  • Вызывает @PreDestroy, destroy-method при shutdown контейнера
public class UserService {
    @PostConstruct
    public void init() {
        System.out.println("Init singleton");
    }

    @PreDestroy
    public void cleanup() {
        System.out.println("Cleanup singleton");  // ВЫЗОВЕТСЯ
    }
}

Prototype:

@Bean
@Scope("prototype")
public UserService userService() {  // scope = prototype
    return new UserService();
}

Spring контейнер:

  • Создаёт новый Bean каждый раз при запросе
  • Управляет жизненным циклом только до момента возврата
  • Вызывает @PostConstruct, init-method
  • НЕ вызывает @PreDestroy, destroy-method

Почему именно так?

1. Ответственность переходит клиенту

Когда вы просите prototype Bean:

@Service
public class OrderService {
    @Autowired
    private UserService userService;  // Получает новый экземпляр
    
    public void processOrder() {
        // userService используется
    }
    // Spring не знает, когда вы закончили с userService!
    // OrderService отвечает за cleanup
}

Проблема: Spring не может знать, когда клиент закончил использовать Bean.

2. Масштабируемость

Если бы Spring отслеживал все prototype Bean'ы:

// Если каждый запрос создаёт prototype Bean
for (int i = 0; i < 1_000_000; i++) {
    UserService service = context.getBean(UserService.class);
    // Spring должен запомнить все 1 млн экземпляров
    // Вызвать destroy() для каждого
    // Невозможно масштабировать!
}

3. Нет единого контейнера

Прототип может быть:

  • Передан в другой класс
  • Сохранён в переменную
  • Затеян в другой Spring контекст
public class UserService {
    @Autowired
    private UserRepository repo;  // Singleton

    @Autowired
    private Logger logger;  // Prototype

    public void save(User user) {
        // Какой код отвечает за cleanup Logger?
        repo.save(user);
        logger.log("User saved");
        // logger.destroy() — кто это вызовет?
    }
}

Решение 1: ObjectFactory для управления

@Service
public class OrderService {
    @Autowired
    private ObjectFactory<UserService> userServiceFactory;

    public void processOrder() {
        UserService service = userServiceFactory.getObject();
        try {
            // Используем service
        } finally {
            // Если UserService реализует DisposableBean
            if (service instanceof DisposableBean) {
                ((DisposableBean) service).destroy();  // Вызываем вручную
            }
        }
    }
}

Решение 2: Реализовать DisposableBean

@Component
@Scope("prototype")
public class UserService implements DisposableBean {
    private Connection connection;

    @PostConstruct
    public void init() {
        this.connection = createConnection();
        System.out.println("Connection opened");
    }

    @Override
    public void destroy() throws Exception {
        if (connection != null) {
            connection.close();  // Вызовите cleanup вручную
            System.out.println("Connection closed");
        }
    }

    private Connection createConnection() {
        // ...
        return null;
    }
}

Решение 3: Request Scope (если используешь Web)

@Bean
@Scope("request")
public UserService userService() {
    return new UserService();
}

Request scope:

  • Создаётся один раз на HTTP request
  • Destroy() ВЫЗОВЕТСЯ при завершении request
  • Идеально для web приложений

Сравнение Scopes

ScopeКогда destroy()Использование
singletonПри shutdown контейнераВЫЗОВЕТСЯ
prototypeНикогдаНЕ ВЫЗОВЕТСЯ
requestПри завершении requestВЫЗОВЕТСЯ
sessionПри завершении sessionВЫЗОВЕТСЯ
applicationПри shutdownВЫЗОВЕТСЯ

Практический пример: Connection Pool

public class DatabaseService {
    private Connection connection;

    @PostConstruct
    public void init() {
        this.connection = DriverManager.getConnection("jdbc:mysql://localhost/db");
        System.out.println("Connection opened");
    }

    @PreDestroy
    public void cleanup() {
        if (connection != null) {
            try {
                connection.close();
                System.out.println("Connection closed");
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

// НЕПРАВИЛЬНО: prototype scope
@Configuration
public class WrongConfig {
    @Bean
    @Scope("prototype")  // Connection утечка!
    public DatabaseService dbService() {
        return new DatabaseService();
    }
}

// ПРАВИЛЬНО: singleton scope
@Configuration
public class RightConfig {
    @Bean
    // @Scope("singleton") — по умолчанию
    public DatabaseService dbService() {
        return new DatabaseService();
        // cleanup() вызовется при shutdown
    }
}

Когда использовать Prototype?

Используй prototype когда:

  • Bean содержит mutable state, специфичный для клиента
  • Bean имеет сложное состояние, зависящее от контекста
  • Клиент полностью отвечает за управление жизненным циклом

НЕ используй prototype когда:

  • Bean имеет ресурсы (connections, streams, threads)
  • Нужен automatic cleanup
  • Все экземпляры используют одни и те же зависимости

Итоговый вывод

destroy() не вызывается для prototype Bean'ов потому что:

  1. Spring не владеет жизненным циклом — ответственность передана клиенту
  2. Масштабируемость — невозможно отслеживать миллионы экземпляров
  3. Прозрачность — Bean может быть передан куда угодно
  4. Философия Prototype — максимальная гибкость, но минимум управления

Это главное различие между singleton (Spring управляет) и prototype (клиент управляет)!