I'm creating an application that has to build up a sizeable in-memory cache before it can accept traffic, so that takes a bit of time. Unfortunately, Boot publishes an AvailabilityChangeEvent<Readiness>
as soon as the context has completed its own setup, so at the moment I'm using an event listener to counteract the first event:
@EventListener
public void handleReadinessStateChangeEvent(AvailabilityChangeEvent<ReadinessState> event) {
if (event.getPayload() == ReadinessState.ACCEPTING_TRAFFIC) {
if (!isCacheFullyInitialized) {
AvailabilityChangeEvent.publish(applicationContext, ReadinessState.REFUSING_TRAFFIC);
}
}
}
This solution seems hacky to me, so I've been digging through the Boot code, but I can't find any toggles or properties that prevent this event from being published prematurely. Is there a better solution that has escaped me?
Comment From: wilkinsona
Unfortunately, Boot publishes an AvailabilityChangeEvent
as soon as the context has completed its own setup
This should not be the case. In addition to the refresh of the context having completed, all ApplicationRunner
and CommandLineRunner
beans should also have been called. The change to accepting traffic is the last thing that's done in SpringApplication.run
. It happens at the same time as the publication of the ApplicationReadyEvent
.
The in-memory cache needs to have been populated by this point, either by performing the work during application context refresh (using InitializingBean
or @PostConstruct
, for example) or in a CommandLineRunner
or ApplicationRunner
bean.
You haven't described how you're populating the in-memory cache. If you're already using one of the approaches suggested above and it isn't working as expected, please provide a minimal sample that reproduces the problem and we can re-open this issue and take another look.
Comment From: ThomasKasene
Thanks for your reply!
The cache in question is being populated by means of a @KafkaListener
method, and the goal is to keep the cache up-to-date by consuming any new records on the fly after initialization. I realized what you're saying makes sense, and that a @KafkaListener
isn't the "proper" thing to use during initialization.
With some trial and error I ended up with this solution, using your InitializingBean
suggestion, as well as the Kafka Consumer
API, and it appears to be working.
@Slf4j
@Configuration
@RequiredArgsConstructor
public class MyCacheInitializer implements InitializingBean {
private final ApplicationProperties applicationProperties; // A custom ConfigurationProperties-class
private final KafkaProperties kafkaProperties;
private final ConsumerFactory<String, Bytes> consumerFactory;
private final MyKafkaMessageProcessor messageProcessor;
@Override
public void afterPropertiesSet() {
String topicName = applicationProperties.getKafka().getConsumer().get("my-consumer").getTopic();
Duration pollTimeout = kafkaProperties.getListener().getPollTimeout();
try (Consumer<String, Bytes> consumer = consumerFactory.createConsumer()) {
consumer.subscribe(List.of(topicName));
log.info("Starting to cache the contents of {}", topicName);
ConsumerRecords<String, Bytes> records;
do {
records = consumer.poll(pollTimeout);
records.forEach(messageProcessor::process);
} while (!records.isEmpty());
}
log.info("Completed caching {}", topicName);
}
}