Ответ
Метод, аннотированный @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()));
}
}
Важные ограничения и «нельзя»:
- Нет гарантий порядка между
@PostConstructразных бинов, если он не задан явно через@DependsOn. - Контекст транзакций ещё не активен. Нельзя выполнять операции, требующие транзакционность.
- Не стоит выполнять долгие операции, так как это задержит полную инициализацию контекста приложения.
- Бин ещё не прошёл через возможные 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()));
}
}
А теперь, блядь, ложка дёгтя, или что НЕЛЬЗЯ:
-
Порядок — хуй его знает. Если у вас десять бинов с
@PostConstruct, Spring будет запускать их как бог на душу положит. Хочет — так, хочет — эдак. Хотите контролировать? Вешайте@DependsOnи мучайтесь. -
Транзакций тут нет, как мамонтов. Контекст транзакций в этот момент обычно ещё спит. Так что если в вашем
initCache()репозиторий вздумает что-то сохранять с@Transactional— это будет печально и не сработает как ожидается. -
Не превращайте это в долгострой. Если в этом методе вы решите скачать весь интернет или рассчитать число Пи до миллиардного знака — всё ваше приложение будет стоять колом и ждать. А оперативка — плакать.
-
Прокси — они ещё в будущем. Ваш бин в этот момент — это ещё чистый, непорочный объект. Spring AOP-прокси (для транзакций, кэширования и прочей хуеты) навешиваются чуть позже. Поэтому если внутри
@PostConstructвы позовёте другой метод этого же бина, expecting, что его перехватит, например,@Transactional— хуй вам, а не перехват. Он вызовется напрямую.
Короче, инструмент простой и мощный, но, как и всё в этом мире, требует понимания, где его хуярить, а где — нет.