Reputation: 33759
How can I defer or change the initial value of Spring Boot's AvailabilityChangeEvent<Readiness>
state?
I have an application that acts as a worker (in our case it is polling messages from an RabbitMQ queue). Moreover, the polling should not be started until some condition has been fulfilled and it should stop again when it is not valid. My idea was to use an @EventListener
for this purpose. It works well in the sense that the worker can be stopped and restarted, however Spring Boot automatically broadcasts an undesired AvailabilityChangeEvent
with state Readiness.ACCEPTING_TRAFFIC
during startup. I have implemented an ApplicationRunner
in an attempt to mitigate this behaviour that should check the condition during startup and submits an AvailabilityChangeEvent
with state Readiness.REFUSING_TRAFFIC
. Furthermore I have implemented a pollCondition()
method annotated with @Scheduled
that will poll a (simulated) condition for state changes:
package com.example.demo;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
@SpringBootApplication
@EnableScheduling
public class DemoApplication {
private static final Logger LOG = LoggerFactory.getLogger(DemoApplication.class);
private boolean condition = false;
private final ApplicationEventPublisher applicationEventPublisher;
public DemoApplication(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
ApplicationRunner runner(ApplicationEventPublisher applicationEventPublisher) {
return ignored -> checkConditionAndBroadcast();
}
@Scheduled(fixedRate = 5, initialDelay = 5, timeUnit = TimeUnit.SECONDS)
void pollCondition() {
// Simulate a condition that changes over time
boolean nextCondition = new Random().nextBoolean();
LOG.info("Polling, next condition: {}", nextCondition);
if (condition != nextCondition) {
condition = nextCondition;
checkConditionAndBroadcast();
}
}
private void checkConditionAndBroadcast() {
if (condition) {
applicationEventPublisher.publishEvent(new AvailabilityChangeEvent<>(this, ReadinessState.ACCEPTING_TRAFFIC));
} else {
applicationEventPublisher.publishEvent(new AvailabilityChangeEvent<>(this, ReadinessState.REFUSING_TRAFFIC));
}
}
/*
* Event listener that will start or stop polling RabbitMQ
*/
@EventListener
public void onApplicationEvent(AvailabilityChangeEvent<ReadinessState> event) {
LOG.info("ReadinessEvent, state: {}", event.getState());
/*
if (event.getState() == ReadinessState.ACCEPTING_TRAFFIC) {
start polling
} else {
stop polling
}
*/
}
}
When running this application, the REFUSING_TRAFFIC
event is sent and received as expected. However, it is immediately followed by an undesired ACCEPTING_TRAFFIC
event that effectively will overrule the first event. In the log below, these two events are submitted from the main
thread. In contrast the other simulated condition changes are submitted from the scheduled-1
thread:
2024-03-15T11:26:15.320+01:00 INFO 9169 --- [demo] [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.768 seconds (process running for 2.286)
2024-03-15T11:26:15.323+01:00 INFO 9169 --- [demo] [ main] com.example.demo.DemoApplication : ReadinessEvent, state: REFUSING_TRAFFIC
2024-03-15T11:26:15.325+01:00 INFO 9169 --- [demo] [ main] com.example.demo.DemoApplication : ReadinessEvent, state: ACCEPTING_TRAFFIC <--- This is undesired since the condition has not yet been met
2024-03-15T11:26:20.330+01:00 INFO 9169 --- [demo] [ scheduling-1] com.example.demo.DemoApplication : Polling, next condition: true
2024-03-15T11:26:20.331+01:00 INFO 9169 --- [demo] [ scheduling-1] com.example.demo.DemoApplication : ReadinessEvent, state: ACCEPTING_TRAFFIC
2024-03-15T11:26:25.319+01:00 INFO 9169 --- [demo] [ scheduling-1] com.example.demo.DemoApplication : Polling, next condition: true
2024-03-15T11:26:30.318+01:00 INFO 9169 --- [demo] [ scheduling-1] com.example.demo.DemoApplication : Polling, next condition: false
2024-03-15T11:26:30.318+01:00 INFO 9169 --- [demo] [ scheduling-1] com.example.demo.DemoApplication : ReadinessEvent, state: REFUSING_TRAFFIC
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
spring.application.name=demo
Are there any other suggestions of how I can leverage Spring (Boot)'s standard lifecycle events or components?
I could implement a custom event for this purpose, but I like the idea of re-using the AvailabilityChangeEvent
for this purpose as the condition should control the state of application (in addition to the RabbitMQ poller). Secondly, I prefer not to couple the domain specific logic of my condition to the RabbitMQ poller which belongs to another domain of the application.
Upvotes: 1
Views: 224