Spring Cloud Gateway:如何在启动时动态设置路由URI
解决Spring Cloud Gateway指定路由启动时通过服务发现一次性替换URI的问题
需求回顾
- 启动阶段一次性替换特定路由的URI,通过服务发现获取真实地址
- 仅对标记过的路由生效(示例中
uri设为https://no.op的路由),以路由id作为服务名查找实例 - 避免使用请求过滤器,消除每次请求的额外检查开销
- 不采用
DiscoveryClient Route Definition Locator方案(全量生效、配置写法不友好)
方案一:自定义BeanPostProcessor处理RouteLocator
通过Bean后置处理器,在RouteLocator Bean初始化完成后,批量修改符合条件的路由URI,实现启动时一次性替换。
配置示例
spring: cloud: gateway: routes: - id: my-portal uri: https://no.op # 标记需要通过服务发现替换的路由 predicates: - Path=/portal/** - id: my-portal-admin uri: https://admin.example.com # 无需替换的路由 predicates: - Path=/admin/**
代码实现
import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; @Component public class ServiceDiscoveryRouteProcessor implements BeanPostProcessor { private final DiscoveryClient discoveryClient; public ServiceDiscoveryRouteProcessor(DiscoveryClient discoveryClient) { this.discoveryClient = discoveryClient; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof RouteLocator) { RouteLocator originalLocator = (RouteLocator) bean; return () -> originalLocator.getRoutes().map(this::replaceUriWithServiceInstance); } return bean; } private Route replaceUriWithServiceInstance(Route route) { // 匹配标记为需要替换的路由 if ("https://no.op".equals(route.getUri().toString())) { String serviceId = route.getId(); // 从服务发现获取实例,此处取第一个实例,可根据负载均衡需求调整 return discoveryClient.getInstances(serviceId).stream() .findFirst() .map(instance -> Route.async() .asyncPredicate(route.getAsyncPredicate()) .filters(route.getFilters()) .id(route.getId()) .order(route.getOrder()) .predicate(route.getPredicate()) .uri(instance.getUri()) .build()) .orElse(route); // 服务不存在时保留原URI,可按需修改逻辑 } return route; } }
方案二:自定义RouteDefinitionRepository
在路由定义加载到内存的阶段直接替换URI,更早完成处理,同样是一次性操作。
代码实现
import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; @Component public class ServiceDiscoveryRouteRepo extends InMemoryRouteDefinitionRepository { private final DiscoveryClient discoveryClient; public ServiceDiscoveryRouteRepo(DiscoveryClient discoveryClient) { this.discoveryClient = discoveryClient; } @Override public Mono<Void> save(Mono<RouteDefinition> route) { return route.map(this::replaceUriIfMarked) .flatMap(super::save); } private RouteDefinition replaceUriIfMarked(RouteDefinition routeDefinition) { if ("https://no.op".equals(routeDefinition.getUri().toString())) { String serviceId = routeDefinition.getId(); discoveryClient.getInstances(serviceId).stream() .findFirst() .ifPresent(instance -> routeDefinition.setUri(instance.getUri())); } return routeDefinition; } }
关键说明
- 服务实例选择:示例中取第一个可用实例,实际可结合
LoadBalancerClient实现负载均衡逻辑,替换实例选择逻辑即可。 - 标记自定义:除了
https://no.op,也可使用其他自定义标记(如uri: service-discovery://trigger),只需调整代码中的匹配条件。 - 容错处理:示例中服务不存在时保留原URI,可根据业务需求改为抛出启动异常或降级到默认地址。
内容的提问来源于stack exchange,提问作者Damo
相关产品推荐
相关产品推荐

