Search code examples
spring-bootnetflix-eurekaspring-cloud-gatewayservice-discovery

Unplanned refresh routes when use Spring Eureka Client in Spring Cloud Gateway


My Gateway is connected to Eureka Service Discovery. Every time a gateway client refresh its state in Eureka, all the routes in gateway are rebuilt. This is critical for me since I have redefined Route Locator and am downloading my routes from another service. How can I disable this behavior or change it?

@Profile({"ui", "internal", "rec"})
@Configuration
public class RouteConfiguration {

    @Bean
    @RefreshScope
    public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder, RouteLoader routeLoader) {
        return new RouteLocatorImpl(routeLocatorBuilder, routeLoader);
    }
}
server.port=30037

spring.application.name=isf-gateway-rec
spring.cloud.loadbalancer.ribbon.enabled=false
eureka.client.service-url.defaultZone= http://127.0.0.1:30035/eureka/

configuration.server.url=http://127.0.0.1:30035

logging.level.root=DEBUG


spring.cloud.gateway.discovery.locator.enabled=false

gateway.load-route.path.all=/routes

EXECUTION_ENV=LOCAL

#Kafka
spring.kafka.bootstrap-servers=127.0.0.1:9092
gateway.kafka.topic=gateway-route-refresh
PARTITION_NUMBER=1
REPLICATION_FACTOR=1
FETCH_MAX_BYTES_CONFIG=5000000
MAX_PARTITION_FETCH_BYTES_CONFIG=5000000
#15 minutes
MAX_POLL_INTERVAL_MS=300000
CONSUMER_SESSION_TIMEOUT_MS=600000
HEARTBEAT_INTERVAL_MS=60000

The whole problem is org.springframework.cloud.gateway.route.RouteRefreshListener. When eureka client creates a HeartbeatEvent, it triggers the rebuilding of routes, is there any way to override this Bean? It does not implement the interface((

public class RouteRefreshListener implements ApplicationListener<ApplicationEvent> {

    private final ApplicationEventPublisher publisher;

    private HeartbeatMonitor monitor = new HeartbeatMonitor();

    public RouteRefreshListener(ApplicationEventPublisher publisher) {
        Assert.notNull(publisher, "publisher may not be null");
        this.publisher = publisher;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ContextRefreshedEvent) {
            ContextRefreshedEvent refreshedEvent = (ContextRefreshedEvent) event;
            if (!WebServerApplicationContext.hasServerNamespace(
                    refreshedEvent.getApplicationContext(), "management")) {
                reset();
            }
        }
        else if (event instanceof RefreshScopeRefreshedEvent
                || event instanceof InstanceRegisteredEvent) {
            reset();
        }
        else if (event instanceof ParentHeartbeatEvent) {
            ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
            resetIfNeeded(e.getValue());
        }
        else if (event instanceof HeartbeatEvent) {
            HeartbeatEvent e = (HeartbeatEvent) event;
            resetIfNeeded(e.getValue());
        }
    }

    private void resetIfNeeded(Object value) {
        if (this.monitor.update(value)) {
            reset();
        }
    }

    private void reset() {
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }
@Bean
    @ConditionalOnClass(
            name = "org.springframework.cloud.client.discovery.event.HeartbeatMonitor")
    public RouteRefreshListener routeRefreshListener(
            ApplicationEventPublisher publisher) {
        return new RouteRefreshListener(publisher);
    }

I tried using the property, but it didn't help spring.cloud.gateway.discovery.locator.enabled=false


Solution

  • I did it using the BeanPostProcessor

    
    @Component
    public class GatewayPostProcessor implements BeanPostProcessor, Ordered {
    
        private final ApplicationEventPublisher publisher;
    
        public GatewayPostProcessor(ApplicationEventPublisher publisher) {
            this.publisher = publisher;
        }
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (beanName.equals("routeRefreshListener")) {
                return new RouteRefreshListenerCustom(publisher);
            } else {
                return bean;
            }
        }
    
        @Override
        public int getOrder() {
            return Ordered.LOWEST_PRECEDENCE;
        }
    }