Что делают методы с аннотацией @PostConstruct и @PreDestroy?
4. Prototype бины — @PreDestroy не вызывается → вручную или DestructionCallback
🟢 Junior Level
@PostConstruct — выполняется один раз после создания бина. @PreDestroy — выполняется один раз перед удалением бина.
Spring 6 / Boot 3: пакет изменился с
javax.annotationнаjakarta.annotation.
@Service
public class MyService {
@PostConstruct
public void init() {
// Вызывается после создания и внедрения зависимостей
System.out.println("Bean создан!");
}
@PreDestroy
public void cleanup() {
// Вызывается перед удалением бина
System.out.println("Bean удаляется!");
}
}
Когда использовать:
@PostConstruct→ инициализация, валидация@PreDestroy→ закрытие соединений, очистка
🟡 Middle Level
Порядок выполнения
1. Конструктор
2. @Autowired (внедрение)
3. @PostConstruct ← Зависимости уже есть!
4. Бин готов
При удалении:
1. @PreDestroy
2. Бин удалён
Почему не конструктор?
// ❌ В конструкторе Field/Setтер зависимости ещё NULL!
public MyService() {
repo.loadData(); // NPE! repo ещё не внедрён
}
// ✅ @PostConstruct — зависимости уже есть
@PostConstruct
public void init() {
repo.loadData(); // OK!
}
Singleton vs Prototype
// Singleton:
// → @PostConstruct вызывается один раз
// → @PreDestroy вызывается при shutdown
// Prototype:
// → @PostConstruct вызывается каждый раз при создании
// → @PreDestroy НЕ вызывается автоматически для Prototype бинов. Spring не отслеживает их уничтожение.
Порядок инициализации
1. @PostConstruct
2. InitializingBean.afterPropertiesSet()
3. @Bean(initMethod = "init")
→ Используйте только @PostConstruct (стандарт)
🔴 Senior Level
Когда НЕ использовать @PostConstruct / @PreDestroy
- Тяжёлая инициализация — блокирует старт контекста → SmartLifecycle
- Вызов @Transactional методов — прокси ещё нет → ContextRefreshedEvent
- Операции с retry — @PostConstruct не retry → SmartLifecycle
- Prototype бины — @PreDestroy не вызывается → вручную или DestructionCallback
Proxy Trap
@PostConstruct вызывается ДО создания прокси!
@Service
public class Service {
@PostConstruct
public void init() {
loadData(); // @Transactional → НЕ работает!
}
@Transactional
public void loadData() { }
}
→ Вызов идёт к оригинальному объекту, не к прокси
→ Транзакция НЕ создаётся
Решение:
@EventListener(ContextRefreshedEvent.class)
public void init() {
loadData(); // Прокси уже есть → транзакция работает
// ContextRefreshedEvent публикуется ПОСЛЕ того, как все бины созданы
// И все прокси тоже. Поэтому вызов @Transactional методов из этого
// обработчика работает корректно.
}
Наследование
class Parent {
@PostConstruct
public void parentInit() { }
}
class Child extends Parent {
// Если не переопределён → вызывается parentInit()
// Если переопределён → вызывается childInit()
}
CommonAnnotationBeanPostProcessor
Отвечает за обработку @PostConstruct и @PreDestroy
→ Регистрируется автоматически
→ Часть стандартного набора BPP
Production Experience
Реальный сценарий: @PreDestroy не вызвался
// Prototype бин с @PreDestroy
@Scope("prototype")
public class Connection {
@PreDestroy
public void close() { } // Никогда не вызовется!
}
// Утечка соединений!
// Решение: вручную вызывать close()
Best Practices
- @PostConstruct → валидация, лёгкая инициализация
- @PreDestroy → очистка ресурсов
- НЕ вызывайте @Transactional методы из @PostConstruct
- Prototype → @PreDestroy не работает
- Exception в @PostConstruct → контекст не поднимется.
Это полезно — приложение fail-fast при неправильной конфигурации. Но: если инициализация может упасть ожидаемо (нет подключения к БД), используйте SmartLifecycle с повторными попытками вместо @PostConstruct.
- ContextRefreshedEvent → когда нужен прокси
Резюме для Senior
- @PostConstruct → после DI, ДО прокси
- @PreDestroy → только для Singleton
- Proxy Trap → @Transactional не работает в @PostConstruct
- ContextRefreshedEvent → когда прокси готов
- Prototype → @PreDestroy никогда не вызывается
- Fail-fast → exception в @PostConstruct = краш старта
🎯 Шпаргалка для интервью
Обязательно знать:
@PostConstruct— выполняется один раз ПОСЛЕ создания бина и внедрения зависимостей, ДО создания прокси@PreDestroy— выполняется один раз ПЕРЕД удалением бина, только для Singleton- В конструкторе Field/Setter зависимости ещё NULL, в
@PostConstruct— уже внедрены @TransactionalНЕ работает в@PostConstruct— прокси ещё не создан, вызов идёт к оригинальному объекту- Prototype:
@PostConstructвызывается каждый раз при создании,@PreDestroyНЕ вызывается автоматически - Exception в
@PostConstruct= приложение не поднимется (fail-fast — полезно при неправильной конфигурации) - Spring 6 / Boot 3:
jakarta.annotation.PostConstructвместоjavax.annotation - Порядок:
@PostConstruct→InitializingBean.afterPropertiesSet()→init-method(используйте только@PostConstruct)
Частые уточняющие вопросы:
- Почему не использовать конструктор для инициализации? В конструкторе Field/Setter зависимости ещё NULL. Для Constructor Injection — зависимости есть, но прокси ещё нет.
- Как вызвать
@Transactionalметод при инициализации?@EventListener(ContextRefreshedEvent.class)— все прокси уже созданы. - Что если тяжёлая инициализация в
@PostConstruct? Блокирует старт контекста — используйтеSmartLifecycleс retry. - Что произойдёт с
@PreDestroyв Prototype? Никогда не вызовется — утечка ресурсов, чистите вручную.
Красные флаги (НЕ говорить):
- «В конструкторе все зависимости уже есть» (для Field/Setter injection — NULL)
- «
@PreDestroyработает для всех scope» (НЕ работает для Prototype) - «Можно вызывать
@Transactionalметоды из@PostConstruct» (прокси ещё не создан, транзакция НЕ создаётся) - «Exception в
@PostConstruct— это плохо» (на самом деле fail-fast полезен, но для ожидаемых ошибок используйтеSmartLifecycle)
Связанные темы:
- [[6. Что такое Bean Lifecycle]]
- [[7. Какие этапы жизненного цикла Bean]]
- [[8. Что такое BeanPostProcessor]]
- [[4. Что такое Bean в Spring]]
- [[10. Что такое scope Bean]]