Питання 9 · Розділ 5

Що роблять методи з анотаціями @PostConstruct та @PreDestroy?

4. Prototype біни — @PreDestroy не викликається → вручну або DestructionCallback

Мовні версії: English Russian Ukrainian

🟢 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

  1. Важка ініціалізація — блокує старт контексту → SmartLifecycle
  2. Виклик @Transactional методів — проксі ще немає → ContextRefreshedEvent
  3. Операції з retry — @PostConstruct не retry → SmartLifecycle
  4. 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

  1. @PostConstruct → валідація, легка ініціалізація
  2. @PreDestroy → очищення ресурсів
  3. НЕ викликайте @Transactional методи з @PostConstruct
  4. Prototype → @PreDestroy не працює
  5. Exception в @PostConstruct → контекст не підніметься.

Це корисно — додаток fail-fast при неправильній конфігурації. Але: якщо ініціалізація може впасти очікувано (немає підключення до БД), використовуйте SmartLifecycle з повторними спробами замість @PostConstruct.

  1. 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
  • Порядок: @PostConstructInitializingBean.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]]