You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何延迟或修改Spring Boot初始Readiness状态的AvailabilityChangeEvent?

问题

如何延迟或修改Spring Boot中AvailabilityChangeEvent<Readiness>状态的初始值?

我有一个作为Worker的Spring Boot应用,负责从RabbitMQ队列轮询消息。轮询需满足特定条件才启动,条件不满足时停止。我计划使用@EventListener来控制轮询的启停,这种方式能正常实现Worker的启停,但Spring Boot启动时会自动广播状态为Readiness.ACCEPTING_TRAFFIC的AvailabilityChangeEvent,这一非预期事件会覆盖我通过ApplicationRunner发送的Readiness.REFUSING_TRAFFIC事件。

我已经实现了ApplicationRunner在启动时检查条件并发送对应状态的AvailabilityChangeEvent,同时实现了带@Scheduled注解的pollCondition()方法模拟条件状态变化:

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() {
        // 模拟随时间变化的条件
        boolean nextCondition = new Random().nextBoolean();
        LOG.info("轮询条件,新状态: {}", 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));
        }
    }

    /*
     * 控制RabbitMQ轮询启停的事件监听器
     */
    @EventListener
    public void onApplicationEvent(AvailabilityChangeEvent<ReadinessState> event) {
        LOG.info("就绪状态事件,当前状态: {}", event.getState());
        /*
         if (event.getState() == ReadinessState.ACCEPTING_TRAFFIC) {
            启动轮询
         } else {
            停止轮询
         }
         */
    }
}

运行应用时,REFUSING_TRAFFIC事件能正常发送和接收,但随即会被启动时的ACCEPTING_TRAFFIC事件覆盖。日志如下:

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         : 就绪状态事件,当前状态: REFUSING_TRAFFIC
2024-03-15T11:26:15.325+01:00  INFO 9169 --- [demo] [           main] com.example.demo.DemoApplication         : 就绪状态事件,当前状态: ACCEPTING_TRAFFIC   <--- 此处不符合预期,因为条件尚未满足
2024-03-15T11:26:20.330+01:00  INFO 9169 --- [demo] [   scheduling-1] com.example.demo.DemoApplication         : 轮询条件,新状态: true
2024-03-15T11:26:20.331+01:00  INFO 9169 --- [demo] [   scheduling-1] com.example.demo.DemoApplication         : 就绪状态事件,当前状态: ACCEPTING_TRAFFIC
2024-03-15T11:26:25.319+01:00  INFO 9169 --- [demo] [   scheduling-1] com.example.demo.DemoApplication         : 轮询条件,新状态: true
2024-03-15T11:26:30.318+01:00  INFO 9169 --- [demo] [   scheduling-1] com.example.demo.DemoApplication         : 轮询条件,新状态: false
2024-03-15T11:26:30.318+01:00  INFO 9169 --- [demo] [   scheduling-1] com.example.demo.DemoApplication         : 就绪状态事件,当前状态: 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/> <!-- 从仓库查找父依赖 -->
    </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

我可以实现自定义事件,但希望复用AvailabilityChangeEvent,因为条件应控制应用状态(除RabbitMQ轮询器外)。其次,我不想将条件的领域特定逻辑与属于应用另一领域的RabbitMQ轮询器耦合。请问有哪些利用Spring(Boot)标准生命周期事件或组件的解决方案?


解决方案

方案1:自定义ReadinessStateIndicator控制初始就绪状态

Spring Boot的就绪状态由ReadinessStateHealthIndicator维护,自定义一个ReadinessStateIndicator可以覆盖默认初始状态,避免启动时自动发送ACCEPTING_TRAFFIC事件。

实现自定义Indicator

import org.springframework.boot.availability.ReadinessState;
import org.springframework.boot.availability.ReadinessStateIndicator;
import org.springframework.stereotype.Component;

@Component
public class CustomReadinessStateIndicator implements ReadinessStateIndicator {

    private volatile ReadinessState state = ReadinessState.REFUSING_TRAFFIC;

    @Override
    public ReadinessState getState() {
        return state;
    }

    public void setState(ReadinessState state) {
        this.state = state;
    }
}

修改状态更新逻辑

注入自定义Indicator,在更新状态时同步修改Indicator的状态:

private final CustomReadinessStateIndicator readinessStateIndicator;

// 构造函数注入
public DemoApplication(ApplicationEventPublisher applicationEventPublisher, CustomReadinessStateIndicator readinessStateIndicator) {
    this.applicationEventPublisher = applicationEventPublisher;
    this.readinessStateIndicator = readinessStateIndicator;
}

private void checkConditionAndBroadcast() {
    ReadinessState targetState = condition ? ReadinessState.ACCEPTING_TRAFFIC : ReadinessState.REFUSING_TRAFFIC;
    readinessStateIndicator.setState(targetState);
    applicationEventPublisher.publishEvent(new AvailabilityChangeEvent<>(this, targetState));
}

这样启动时初始状态就是REFUSING_TRAFFIC,不会触发默认的ACCEPTING_TRAFFIC事件。

方案2:监听ApplicationReadyEvent后设置初始状态

ApplicationReadyEvent在Spring Boot启动完成后触发,晚于默认的ACCEPTING_TRAFFIC事件。放弃ApplicationRunner,改用该事件监听器发送自定义状态,覆盖默认初始值。

修改代码

移除原ApplicationRunner Bean,添加ApplicationReadyEvent监听器:

// 移除原来的ApplicationRunner
// @Bean
// ApplicationRunner runner(ApplicationEventPublisher applicationEventPublisher) {
//     return ignored -> checkConditionAndBroadcast();
// }

@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
    checkConditionAndBroadcast();
}

此时启动流程中会先收到默认的ACCEPTING_TRAFFIC事件,随后你的自定义事件会覆盖状态,最终结果符合预期。

方案3:在监听器中过滤非预期事件

记录当前预期的状态,在监听器中忽略不符合预期的默认事件,只处理你主动发送的状态变更。

修改代码

private ReadinessState expectedState = ReadinessState.REFUSING_TRAFFIC;

private void checkConditionAndBroadcast() {
    expectedState = condition ? ReadinessState.ACCEPTING_TRAFFIC : ReadinessState.REFUSING_TRAFFIC;
    applicationEventPublisher.publishEvent(new AvailabilityChangeEvent<>(this, expectedState));
}

@EventListener
public void onApplicationEvent(AvailabilityChangeEvent<ReadinessState> event) {
    if (event.getState() != expectedState) {
        // 忽略默认的ACCEPTING_TRAFFIC事件
        return;
    }
    LOG.info("就绪状态事件,当前状态: {}", event.getState());
    // 处理轮询启停逻辑
}

这种方式下,默认事件会被直接过滤,只有符合你预期的状态变更才会触发业务逻辑。


内容的提问来源于Stack Exchange,提问作者matsev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 19:19:50