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

Можно ли запустить PreDestroy в Prototype?

2.0 Middle🔥 71 комментариев
#Docker, Kubernetes и DevOps#Основы Java

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

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

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

Можно ли запустить @PreDestroy в Prototype scope

Ответ: Технически можно, но @PreDestroy НЕ БУДЕТ ВЫЗВАН автоматически для Prototype бинов. Вот почему и как это исправить:

1. Различие между Singleton и Prototype scopes

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

@Component
@Scope("singleton")  // По умолчанию
public class SingletonService {
    
    @PostConstruct
    public void init() {
        System.out.println("Singleton: init called");
    }
    
    @PreDestroy
    public void destroy() {
        System.out.println("Singleton: destroy called");  // ✓ БУДЕТ ВЫЗВАН
    }
}

Prototype (NEW каждый раз)

@Component
@Scope("prototype")  // ← Новый объект каждый раз
public class PrototypeService {
    
    @PostConstruct
    public void init() {
        System.out.println("Prototype: init called");  // ✓ БУДЕТ ВЫЗВАН
    }
    
    @PreDestroy
    public void destroy() {
        System.out.println("Prototype: destroy called");  // ✗ НЕ БУДЕТ ВЫЗВАН!
    }
}

2. Почему @PreDestroy не работает с Prototype

Жизненный цикл разных scopes

public class BeanLifecycleComparison {
    
    public void explainDifference() {
        /*
        SINGLETON (Spring управляет полностью):
        ┌─────────────────────────────────────────┐
        │ Spring ApplicationContext               │
        │                                         │
        │ 1. create() → new instance              │
        │ 2. @PostConstruct → вызывается          │
        │ 3. использование                        │
        │ 4. context.close()                      │
        │ 5. @PreDestroy → вызывается ✓           │
        │ 6. очистка ресурсов                     │
        └─────────────────────────────────────────┘
        
        PROTOTYPE (ты управляешь):
        ┌─────────────────────────────────────────┐
        │ Spring ApplicationContext               │
        │                                         │
        │ 1. getBean() → new instance             │
        │ 2. @PostConstruct → вызывается          │
        │ 3. вот тебе инстанс (дальше ты отвечаешь)
        │                                         │
        │ Spring НЕ знает когда ты закончишь     │
        │ Spring НЕ вызовет @PreDestroy ✗        │
        │ ТЫ должен вызвать destroy() сам        │
        └─────────────────────────────────────────┘
        */
    }
}

3. Демонстрация проблемы

Пример: @PreDestroy НЕ вызывается

@Component
@Scope("prototype")
public class DatabaseConnection {
    
    private Connection connection;
    
    @PostConstruct
    public void init() {
        System.out.println("Establishing DB connection...");
        // connection = DriverManager.getConnection(...);
    }
    
    @PreDestroy
    public void closeConnection() {
        System.out.println("Closing DB connection");  // ✗ Никогда не выведется!
        // if (connection != null) connection.close();
    }
    
    public void query(String sql) {
        System.out.println("Executing: " + sql);
    }
}

// Использование
@Service
public class MyService {
    
    @Autowired
    private ApplicationContext context;
    
    public void doSomething() {
        DatabaseConnection conn = context.getBean(DatabaseConnection.class);
        // Output: "Establishing DB connection..."
        
        conn.query("SELECT * FROM users");
        
        // Когда закончим, соединение остается открытым!
        // @PreDestroy никогда не вызывается
        // Утечка ресурсов!
    }
}

4. Решения

Решение 1: Вызвать destroy() вручную

@Component
@Scope("prototype")
public class PrototypeWithManualDestroy {
    
    @PostConstruct
    public void init() {
        System.out.println("Initialized");
    }
    
    @PreDestroy
    public void destroy() {
        System.out.println("Destroyed");  // ← Вызовется, если вызвать вручную
    }
    
    // Альтернативный метод для явного вызова
    public void cleanup() {
        destroy();
    }
}

@Service
public class Service1 {
    
    @Autowired
    private ObjectFactory<PrototypeWithManualDestroy> factory;
    
    public void doSomething() {
        PrototypeWithManualDestroy proto = factory.getObject();
        // Output: "Initialized"
        
        // ... использование ...
        
        // Вручную вызываем cleanup
        proto.cleanup();  // Вызовет @PreDestroy
        // Output: "Destroyed" ✓
    }
}

Решение 2: Использовать DestructionAwareBeanPostProcessor

import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;

@Component
public class PrototypeDestructionProcessor implements DestructionAwareBeanPostProcessor {
    
    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) {
        // Вызывается для ВСЕХ бинов перед их уничтожением
        if (bean instanceof PrototypeService) {
            System.out.println("Destroying prototype bean: " + beanName);
            // Здесь можно вызвать cleanup логику
        }
    }
}

// Использование
@Component
@Scope("prototype")
public class PrototypeService implements DestructionAwareBeanPostProcessor {
    
    @PostConstruct
    public void init() {
        System.out.println("Init");
    }
    
    @PreDestroy
    public void destroy() {
        System.out.println("Destroy");  // Будет вызван при правильной обработке
    }
}

Решение 3: Использовать ObjectFactory с явным управлением

@Service
public class ProtoypeManagementService {
    
    @Autowired
    private ObjectFactory<PrototypeBean> factory;
    
    private List<PrototypeBean> createdBeans = new ArrayList<>();
    
    public PrototypeBean createAndTrack() {
        PrototypeBean bean = factory.getObject();
        createdBeans.add(bean);  // ← Отслеживаем созданные бины
        return bean;
    }
    
    // При завершении
    @PreDestroy
    public void cleanupAllBeans() {
        for (PrototypeBean bean : createdBeans) {
            bean.destroy();  // Вызываем destroy для каждого
        }
    }
}

Решение 4: Использовать Scope с настраиваемым режимом

import org.springframework.beans.factory.config.Scope;

@Configuration
public class CustomScopeConfig {
    
    @Bean
    public static CustomScope customScope() {
        return new CustomScope();
    }
}

public class CustomScope implements Scope {
    
    private final Map<String, Object> objects = new HashMap<>();
    private final Map<String, Runnable> destructionCallbacks = new HashMap<>();
    
    @Override
    public Object get(String name, ObjectFactory<?> factory) {
        return objects.computeIfAbsent(name, k -> factory.getObject());
    }
    
    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        destructionCallbacks.put(name, callback);
    }
    
    public void close() {
        // Явно вызываем все callbacks при закрытии
        destructionCallbacks.values().forEach(Runnable::run);
        objects.clear();
    }
}

5. Best Practice: Когда использовать Prototype

Проблема

// ✗ ПЛОХО: Создавать prototype для каждого запроса
@Component
@Scope("prototype")
public class RequestService {
    
    @PreDestroy
    public void cleanup() {
        // Никогда не вызовется автоматически
    }
}

@RestController
public class MyController {
    
    @Autowired
    private ApplicationContext context;
    
    @GetMapping("/api/data")
    public ResponseEntity<?> getData() {
        RequestService service = context.getBean(RequestService.class);
        // ✗ Утечка: cleanup() никогда не вызовется
    }
}

Решение: Request scope вместо Prototype

// ✓ ХОРОШО: Использовать Request scope
@Component
@Scope("request")
public class RequestService {
    
    @PreDestroy
    public void cleanup() {
        // ✓ БУДЕТ ВЫЗВАН в конце request lifecycle
    }
}

@RestController
public class MyController {
    
    @Autowired
    private RequestService service;  // ✓ Spring будет управлять
    
    @GetMapping("/api/data")
    public ResponseEntity<?> getData() {
        // service автоматически cleanup'ится
    }
}

6. Таблица: Жизненные циклы разных scopes

Scope@PostConstruct@PreDestroyУправлениеКогда использовать
singletonSpringСервисы, repositories, конфиги
prototype✗*ТыStateful объекты, неустойчивые к concurrent access
requestSpringЛогика, специфичная для request
sessionSpringДанные пользователя (в web app)
websocketSpringWebSocket endpoints

*Для Prototype нужно вызывать @PreDestroy вручную или использовать ObjectFactory

7. Рекомендация: Singleton vs Prototype

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

@Component  // ← Singleton по умолчанию
public class UserRepository {
    // Stateless, thread-safe
    // @PreDestroy будет вызван автоматически
}

@Component
public class EmailService {
    // Stateless
    // @PreDestroy будет вызван автоматически
}

Когда использовать PROTOTYPE (редко)

@Component
@Scope("prototype")
public class UserRequest {
    // Содержит данные конкретного request
    // Нельзя шарить между потоками
    // @PreDestroy потребует специальной обработки
}

// Лучше вариант:
@Component
@Scope("request")  // ← Используй request scope вместо prototype
public class UserRequest {
    // @PreDestroy будет вызван автоматически
}

8. Полный пример: Правильное управление ресурсами

// BAD: Prototype с утечкой ресурсов
@Component
@Scope("prototype")
public class BadDatabaseConnection {
    private Connection connection;
    
    @PostConstruct
    public void init() {
        // connection = createConnection();
    }
    
    @PreDestroy
    public void destroy() {
        // Никогда не будет вызван!
    }
}

// GOOD: Singleton с управлением циклом жизни
@Component
public class GoodDatabaseConnection {
    private final ConnectionPool pool;
    
    @PostConstruct
    public void init() {
        pool.initialize();
    }
    
    @PreDestroy
    public void destroy() {
        pool.close();  // ✓ Будет вызван при shutdown
    }
    
    public Connection getConnection() {
        return pool.acquire();
    }
}

// GOOD ALTERNATIVE: Request scope для request-specific логики
@Component
@Scope("request")
public class RequestContext {
    private String requestId;
    
    @PostConstruct
    public void init() {
        this.requestId = UUID.randomUUID().toString();
    }
    
    @PreDestroy
    public void cleanup() {
        // ✓ Вызовется в конце request
    }
}

Итоговый ответ

Можно добавить @PreDestroy к Prototype бину, но он НЕ БУДЕТ ВЫЗВАН автоматически!

Почему:

Singleton = Spring управляет полным жизненным циклом
           → @PreDestroy вызывается при context.close()

Prototype = Spring создает объект, но ты им управляешь
           → Spring НЕ знает когда ты закончишь
           → @PreDestroy должен вызвать ТЫ

Решения:

  1. Вызвать destroy() вручную

    proto.destroy();  // Вызовет @PreDestroy
    
  2. Использовать ObjectFactory для отслеживания

    ObjectFactory<Proto> factory;
    Proto proto = factory.getObject();
    
  3. Использовать Request/Session scope вместо Prototype

    @Scope("request")  // @PreDestroy вызовется автоматически
    
  4. Использовать DestructionAwareBeanPostProcessor

ВАЖНОЕ ПРАВИЛО:

Производное правило:
- Используй Singleton для stateless компонентов (default)
- Используй @Scope("request") для request-specific логики
- Избегай Prototype, если не понимаешь жизненный цикл