前沿
Spring Cloud Gateway (下文简称 Gateway)作为微服务网关,动态配置路由是刚需,毕竟没人想路由一改就要重启网关。
本文结合源码,提供一种动态路由配置的思路。
何为路由
在 Gateway 中,路由对应的类是org.springframework.cloud.gateway.route.Route
,各属性含义:
- id:路由唯一标识
- uri:转发的目标地址
- order:路由顺序
- predicate:谓词集合,符合条件的请求才会被路由
- gatewayFilters:过滤器集合,允许对请求转发前后对请求和响应做修改
- metadata:路由元数据
java
public class Route implements Ordered {
private final String id;
private final URI uri;
private final int order;
private final AsyncPredicate<ServerWebExchange> predicate;
private final List<GatewayFilter> gatewayFilters;
private final Map<String, Object> metadata;
}
路由是怎么来的?
Gateway 提供了org.springframework.cloud.gateway.route.RouteLocator
接口用来构建路由。
java
public interface RouteLocator {
Flux<Route> getRoutes();
}

子类有三个:
CachingRouteLocator
通过 ConcurrentHashMap 来缓存路由对象,避免路由对象反复构建,通过监听 RefreshRoutesEvent 事件来刷新缓存,主要用于提升性能。
CompositeRouteLocator
RouteLocator 的装饰器,将一组 RouteLocator 合并成一个。
RouteDefinitionRouteLocator
路由的实际构建者,根据路由定义对象 RouteDefinition 来构建路由,内部会依赖 RoutePredicateFactory,GatewayFilterFactory 工厂类。
RouteDefinition
为了构建路由,Gateway 提供了org.springframework.cloud.gateway.route.RouteDefinition
路由定义类,根据 RouteDefinition 来构建 Route。
RouteDefinition 是怎么来的?
和构建路由一样,Gateway 也提供了 RouteDefinitionLocator 接口来发现 RouteDefinition。
java
public interface RouteDefinitionLocator {
Flux<RouteDefinition> getRouteDefinitions();
}

常见的子类有:
PropertiesRouteDefinitionLocator
基于配置文件的实现,它会从配置文件中读取spring.cloud.gateway.routes
来构建 RouteDefinition,当然也支持从配置中心读取。
InMemoryRouteDefinitionRepository
基于内存的实现,内部持有一个 Map 对象来管理 RouteDefinition,默认是空的。额外实现了 RouteDefinitionWriter 接口,允许新增和删除 RouteDefinition。
DiscoveryClientRouteDefinitionLocator
基于服务发现的实现,支持从服务注册中心发现服务,并将服务构建成 RouteDefinition。
CompositeRouteDefinitionLocator
装饰器实现,用于将多个 RouteDefinitionLocator 合并成一个。
RouteLocator工作流程
路由的构建依赖两个核心组件:RouteLocator 和 RouteDefinitionLocator。
简单来说,RouteDefinitionLocator 负责提供 RouteDefinition 路由定义对象,RouteLocator 再根据 RouteDefinition 构建路由对象。

项目引入spring-cloud-starter-gateway
依赖后,Spring 启动时会触发 Gateway 的自动装配,
即org.springframework.cloud.gateway.config.GatewayAutoConfiguration
类。
Gateway 路由定义发现依赖一组 RouteDefinitionLocator,再把他们包装成 CompositeRouteDefinitionLocator
java
@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {
return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
}
默认的 RouteDefinitionLocator 有两个,分别是:PropertiesRouteDefinitionLocator 和 InMemoryRouteDefinitionRepository。前者读取配置文件来生成 RouteDefinition,后者使用 Map 来维护,默认是空的。
java
@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
return new PropertiesRouteDefinitionLocator(properties);
}
@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
return new InMemoryRouteDefinitionRepository();
}
RouteDefinitionLocator 有了,接下来就是 RouteLocator。默认会自动装配 RouteDefinitionRouteLocator 和 CachingRouteLocator,前者根据 RouteDefinition 来构建路由,后者提供缓存优化性能。
java
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> predicates,
RouteDefinitionLocator routeDefinitionLocator, ConfigurationService configurationService) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, gatewayFilters, properties,
configurationService);
}
@Bean
@Primary
@ConditionalOnMissingBean(name = "cachedCompositeRouteLocator")
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}
实现动态路由,CachingRouteLocator 是关键。
默认情况下,Gateway 会通过 CachingRouteLocator 来优化性能。因为根据 RouteDefinition 来构建路由还是有不小开销的,CachingRouteLocator 会把构建好的 List 缓存下来。
同时,它实现了 ApplicationListener,通过监听 RefreshRoutesEvent 事件来刷新路由。

动态路由实现
综上所述,实现动态路由的思路就是:
实现一个自定义的 RouteDefinitionLocator 从远程加载配置并构建 RouteDefinition,路由一旦有变更,就发送一个 RefreshRoutesEvent 事件通知 CachingRouteLocator 刷新路由,即可生效。
路由配置推荐 Nacos,因为它有变更通知。通过监听配置,即可在配置变更时,第一时间刷新路由,让新的路由生效。
首先,引入依赖
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
然后,实现自定义的 RouteDefinitionLocator,目的是从 Nacos 读取路由配置,构建 RouteDefinition。这里以 JSON 格式配置,routesJson
启动时由 Nacos 负责初始化配置,routesListener()
方法在配置变更时触发,关键是变更后发送 RefreshRoutesEvent 事件,否则缓存不会刷新。
java
@Component
public class DynamicRouteDefinitionLocator implements RouteDefinitionLocator {
@NacosConfig(dataId = "gateway-routes.json", group = "DEFAULT_GROUP")
private String routesJson;
private final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@NacosConfigListener(dataId = "gateway-routes.json", group = "DEFAULT_GROUP")
public void routesListener(String data) {
this.routesJson = data;
// 发送路由刷新事件,getRouteDefinitions()会重新触发
eventPublisher.publishEvent(new RefreshRoutesEvent(this));
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
try {
return Flux.fromIterable(OBJECT_MAPPER.readerForListOf(RouteDefinition.class).readValue(routesJson));
} catch (JsonProcessingException e) {
return Flux.empty();
}
}
}
在 Nacos 中配置,Data ID=gateway-routes.json,Group=DEFAULT_GROUP,内容如下:
json
[
{
"id": "tb_route",
"uri": "https://www.taobao.com",
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/tb/**"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args": {
"parts": "1"
}
}
]
}
]
网关启动,访问127.0.0.1:8080/tb
的请求会被转发到www.taobao.com
。
修改路由配置如下,网关无需重启,访问127.0.0.1:8080/jd
的请求会被转发到www.jd.com
。
json
[
{
"id": "jd_route",
"uri": "https://www.jd.com/",
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/jd/**"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args": {
"parts": "1"
}
}
]
}
]
尾巴
Spring Cloud Gateway 动态路由实现的核心思路,是通过自定义 RouteDefinitionLocator 从 Nacos 等配置中心动态加载路由定义,并在配置变更时发布 RefreshRoutesEvent 事件触发 CachingRouteLocator 刷新路由缓存。