Что гарантированно проинициализировано к моменту вызова метода, помеченного @PostConstruct в Spring?

Ответ

Метод, аннотированный @PostConstruct, вызывается после того, как Spring полностью внедрит все зависимости (проведёт инъекцию) в бин, но до того, как бин будет готов к использованию клиентом.

С чем можно взаимодействовать:

  • Все поля бина, помеченные @Autowired, @Inject или @Resource. К этому моменту они уже проинициализированы.
  • Состояние самого бина. Вы можете безопасно выполнять настройку на основе инжектированных зависимостей.

Пример:

@Service
public class CacheService {

    @Autowired
    private SomeRepository repository; // Уже инжектирован
    private Map<String, Data> cache;

    @PostConstruct
    public void initCache() {
        // Можно использовать repository
        List<Data> data = repository.findAll();
        this.cache = data.stream()
                         .collect(Collectors.toMap(Data::getId, Function.identity()));
    }
}

Важные ограничения и «нельзя»:

  1. Нет гарантий порядка между @PostConstruct разных бинов, если он не задан явно через @DependsOn.
  2. Контекст транзакций ещё не активен. Нельзя выполнять операции, требующие транзакционность.
  3. Не стоит выполнять долгие операции, так как это задержит полную инициализацию контекста приложения.
  4. Бин ещё не прошёл через возможные Spring AOP-прокси, поэтому вызовы методов внутри @PostConstruct не будут перехватываться advice'ами (если только это не self-injection).

Ответ 18+ 🔞

Давайте разберём этот ваш @PostConstruct, а то народ, бывает, думает, что это какая-то магия, а там всё просто, как три копейки.

Представьте, что Spring — это такой дотошный снабженец на складе. Сначала он приносит и раскидывает по полочкам (то есть по полям вашего бина) все банки с краской, кисти и холсты, которые вы заказали через @Autowired. Всё аккуратненько, всё на месте.

И вот, когда последняя баночка стоит на своей полке, снабженец говорит: «Так, мудила, всё принёс. Теперь, пока я не ушёл, можешь сразу начать свою поделку — размешать краски, подготовить палитру, в общем, сделать что надо с тем, что я тебе только что вручил».

Вот этот момент — «пока я не ушёл, делай что надо» — и есть @PostConstruct. Метод с этой аннотацией Spring дернет после внедрения всех зависимостей, но до того, как отдаст ваш бин кому-то другому в работу.

Что можно делать? Да всё, что угодно с тем, что уже есть! Все эти ваши @Autowired-поля — они уже не пустые, в них уже сидят настоящие, живые бины. Можете их дёргать, настраивать кэши, заполнять мапы — полный карт-бланш.

@Service
public class CacheService {

    @Autowired
    private SomeRepository repository; // Уже не null, а самый что ни на есть репозиторий!
    private Map<String, Data> cache;

    @PostConstruct
    public void initCache() {
        // Репозиторий уже готов, можно смело долбить базу
        List<Data> data = repository.findAll();
        this.cache = data.stream()
                         .collect(Collectors.toMap(Data::getId, Function.identity()));
    }
}

А теперь, блядь, ложка дёгтя, или что НЕЛЬЗЯ:

  1. Порядок — хуй его знает. Если у вас десять бинов с @PostConstruct, Spring будет запускать их как бог на душу положит. Хочет — так, хочет — эдак. Хотите контролировать? Вешайте @DependsOn и мучайтесь.

  2. Транзакций тут нет, как мамонтов. Контекст транзакций в этот момент обычно ещё спит. Так что если в вашем initCache() репозиторий вздумает что-то сохранять с @Transactional — это будет печально и не сработает как ожидается.

  3. Не превращайте это в долгострой. Если в этом методе вы решите скачать весь интернет или рассчитать число Пи до миллиардного знака — всё ваше приложение будет стоять колом и ждать. А оперативка — плакать.

  4. Прокси — они ещё в будущем. Ваш бин в этот момент — это ещё чистый, непорочный объект. Spring AOP-прокси (для транзакций, кэширования и прочей хуеты) навешиваются чуть позже. Поэтому если внутри @PostConstruct вы позовёте другой метод этого же бина, expecting, что его перехватит, например, @Transactional — хуй вам, а не перехват. Он вызовется напрямую.

Короче, инструмент простой и мощный, но, как и всё в этом мире, требует понимания, где его хуярить, а где — нет.