Що роблять методи з анотаціями @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/Setter залежності ще 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 lifecycle]]
- [[8. Що таке BeanPostProcessor]]
- [[4. Що таке Bean в Spring]]
- [[10. Що таке Bean scope]]