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

Какие знаешь ситуации, когда метод с аннотацией PreDestroy никогда не сработает у Bean в Spring?

3.0 Senior🔥 51 комментариев
#Spring Framework

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

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

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

Ответ

Отличный вопрос о жизненном цикле Bean'ов в Spring! Есть несколько подводных камней, когда @PreDestroy не будет вызван.

Ситуация 1: Bean не управляется Spring контейнером

@PreDestroy вызывается только для Bean'ов, которыми управляет Spring контейнер. Если ты создаешь объект через new, то метод @PreDestroy никогда не сработает.

@Component
public class MyService {
    @PreDestroy
    public void cleanup() {
        System.out.println("Cleaning up");
    }
}

@Component
public class BadExample {
    public void doSomething() {
        // НЕПРАВИЛЬНО: объект создается через new
        MyService service = new MyService();
        // @PreDestroy НЕ будет вызван!
    }
}

@Component
public class GoodExample {
    @Autowired
    private MyService service;  // ПРАВИЛЬНО: управляется Spring
    // @PreDestroy БУДЕТ вызван!
}

Ситуация 2: Области видимости (Scope) Bean'ов

Для prototype-scoped Bean'ов Spring не вызывает методы уничтожения. Это потому что Spring не управляет полным жизненным циклом prototype Bean'ов.

@Component
@Scope("prototype")
public class PrototypeService {
    @PreDestroy
    public void cleanup() {
        System.out.println("This won't be called!");
    }
}

@Component
@Scope("singleton")
public class SingletonService {
    @PreDestroy
    public void cleanup() {
        System.out.println("This will be called");
    }
}

Почему? Для prototype Bean'ов создаются новые экземпляры при каждом запросе. Spring не отслеживает все экземпляры и не может вызвать методы уничтожения.

Решение: если нужна очистка для prototype Bean'ов, используй ObjectFactory или ObjectProvider с callback'ами.

@Component
public class PrototypeBeanManager {
    @Autowired
    private ObjectProvider<PrototypeService> serviceProvider;
    
    public void usePrototypeBean() {
        PrototypeService service = serviceProvider.getObject();
        try {
            // используй сервис
        } finally {
            // ручная очистка
            service.cleanup();
        }
    }
}

Ситуация 3: Жесткое завершение JVM (System.exit())

Если приложение завершается аварийно или вызывается System.exit(), методы @PreDestroy могут не быть вызваны.

@Component
public class DatabaseConnection {
    @PreDestroy
    public void closeConnection() {
        System.out.println("Closing DB connection");
    }
}

@Component
public class BadShutdown {
    public void crash() {
        System.exit(1);  // @PreDestroy может не вызваться
    }
}

Решение: используй Shutdown Hook'и для критичных операций.

public void registerShutdownHook() {
    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
        System.out.println("Shutdown hook called");
        // очистка
    }));
}

Ситуация 4: Исключения в конструкторе или методах инициализации

Если Bean не был полностью инициализирован из-за исключения, то @PreDestroy может не быть вызван.

@Component
public class FaultyBean {
    public FaultyBean() {
        throw new RuntimeException("Initialization failed");
    }
    
    @PreDestroy
    public void cleanup() {
        System.out.println("This won't be called");
    }
}

Решение: используй @PostConstruct для инициализации и правильной обработки ошибок.

@Component
public class BetterBean {
    @PostConstruct
    public void init() {
        if (someCondition) {
            throw new IllegalStateException("Cannot initialize");
        }
    }
    
    @PreDestroy
    public void cleanup() {
        System.out.println("This will be called if init succeeded");
    }
}

Ситуация 5: Неправильное использование аннотации

Метод @PreDestroy должен отвечать определенным требованиям:

@Component
public class IncorrectPreDestroy {
    // ✅ Правильно
    @PreDestroy
    public void cleanup() {}
    
    // ❌ Неправильно: параметры
    @PreDestroy
    public void cleanup(String param) {}
    
    // ❌ Неправильно: возвращаемый тип должен быть void
    @PreDestroy
    public String cleanup() { return "ok"; }
    
    // ❌ Неправильно: статический метод
    @PreDestroy
    public static void cleanup() {}
    
    // ❌ Неправильно: на private методе (хотя работает, но плохо)
    @PreDestroy
    private void cleanup() {}
}

Ситуация 6: Lazy-initialized Bean

Ленивые Bean'ы могут никогда не быть инициализированы, если они не используются.

@Component
@Lazy
public class LazyService {
    @PreDestroy
    public void cleanup() {
        System.out.println("This won't be called if Bean never used");
    }
}

@Component
public class Consumer {
    @Autowired
    @Lazy
    private LazyService service;  // Bean инициализируется при первом доступе
    
    public void use() {
        service.doSomething();  // Инициализация происходит здесь
    }
}

Ситуация 7: Циклические зависимости

В редких случаях циклические зависимости могут помешать правильной инициализации.

@Component
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    
    @PreDestroy
    public void cleanup() {}
}

@Component
public class ServiceB {
    @Autowired
    private ServiceA serviceA;  // Циклическая зависимость
    
    @PreDestroy
    public void cleanup() {}
}

Решение: используй @Lazy или конструкторную инъекцию для управления зависимостями.

Ситуация 8: Многопоточность и race conditions

Если есть проблемы с потокобезопасностью, метод @PreDestroy может не выполниться корректно.

@Component
public class ThreadUnsafeBean {
    private volatile boolean initialized = false;
    
    @PostConstruct
    public void init() {
        initialized = true;
    }
    
    @PreDestroy
    public void cleanup() {
        if (initialized) {
            // очистка
        }
    }
}

Чеклист для гарантии вызова @PreDestroy

  • ✅ Bean создан аннотацией @Component, @Service, @Bean и т.д.
  • ✅ Bean в scope singleton (по умолчанию)
  • ✅ Метод не принимает параметров
  • ✅ Метод возвращает void
  • ✅ Метод не статичный
  • ✅ Bean полностью инициализировался
  • ✅ Spring контейнер завершается нормально (не System.exit())
  • ✅ Нет исключений при создании Bean'а

Главный вывод: всегда управляй критичными ресурсами через Spring контейнер и используй правильные scope'ы для Bean'ов.