Вопрос 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/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

  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]]
  • [[8. Что такое BeanPostProcessor]]
  • [[4. Что такое Bean в Spring]]
  • [[10. Что такое scope Bean]]