Spring Cloud Gateway中的RouteLocator

在使用Spring Cloud Gateway时,需要配置路由,以便将不同的请求转发到不同的服务上。路由配置有两种,一是在配置文件中配置,而是自定义RouteLocator来进行代码配置。当请求到达Spring Cloud Gateway时,会获取所有路由信息,然后通过断言来匹配路由。本文就来分析一下Spring Cloud Gateway是怎么实现路由管理的。


CachingRouteLocator

创建过程

RoutePredicateHandlerMapping中,被注入的是CachingRouteLocator类型的对象。这个类采用装饰器模式封装了其他的路由定位器,目的是在创建时就获取路由信息。

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteLocator.java
private static final String CACHE_KEY = "routes";

private final RouteLocator delegate;

private final Flux<Route> routes;

private final Map<String, List> cache = new ConcurrentHashMap<>();

private ApplicationEventPublisher applicationEventPublisher;
public CachingRouteLocator(RouteLocator delegate) {
    this.delegate = delegate;
    /*
     * 获取路由信息
     * 从cache中获取routes键对应的值,如果不存在,则调用fetch方法来获取
     */
    routes = CacheFlux.lookup(cache, CACHE_KEY, Route.class).onCacheMissResume(this::fetch);
}
private Flux<Route> fetch() {
    // 获取路由并排序
    return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);
}

现在的问题就是这个委托对象是什么?找到这个问题的答案就要找到创建时传递给构造方法的参数是什么。同样地,在GatewayAutoConfiguration中会注册该类型的bean。

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java
/*
 * 该bean的优先级较高,将上面routeDefinitionRouteLocator的返回值先封装为CompositeRouteLocator,
 * 然后再封装为CachingRouteLocator,
 * CompositeRouteLocator和CachingRouteLocator都是RouteLocator的另外两个特殊的实现类。
 *
 * RouteDefinitionRouteLocator在调用getRoutes时才对路由信息进行处理,
 * 所以CachingRouteLocator会在初始化时就调用每个RouterLocator的getRoutes组装好所有的route对象并进行缓存,
 * 以供RoutePredicateHandlerMapping调用。
 */
@Bean
@Primary
@ConditionalOnMissingBean(name = "cachedCompositeRouteLocator")
// TODO: property to disable composite?
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
    /*
     * 默认情况下,这里的routeLocators包含两个元素,除了上面的routeDefinitionRouteLocator方法创建的bean外,
     * 还有RouteLocatorBuilder$Builder中的lambda(暂时没找到是哪里出入这个的)。
     */
    return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}

这里是将多个RouteLocator对象封装进入CompositeRouteLocator,默认情况下GatewayAutoConfiguration就会注册一个真正的路由定位器RouteDefinitionRouteLocator,当然用户可以通过代码配置来自定义。

实际上Spring的bean工厂中有多个类型的RouteLocator,那为什么被注入RoutePredicateHandlerMapping中的是CachingRouteLocator呢?这是因为在其bean方法上加了@Primary注解,导致这个bean的优先级比其他同类型的高。

获取路由

被调用的获取路由的方法如下,就是直接返回构造方法中被设置的routes属性。

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteLocator.java
@Override
public Flux<Route> getRoutes() {
    return this.routes;
}

CompositeRouteLocator

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteLocator.java
public class CompositeRouteLocator implements RouteLocator {

	private final Flux<RouteLocator> delegates;

	public CompositeRouteLocator(Flux<RouteLocator> delegates) {
		this.delegates = delegates;
	}

	@Override
	public Flux<Route> getRoutes() {
		// 从多个路由定位器中获取路由信息
		return this.delegates.flatMapSequential(RouteLocator::getRoutes);
	}

	@Override
	public Flux<Route> getRoutesByMetadata(Map<String, Object> metadata) {
		return this.delegates.flatMapSequential(routeLocator -> routeLocator.getRoutesByMetadata(metadata));
	}

}

这个类的实现很简单,委托给多个路由定位器对象来获取路由信息。下面主要来分析一下框架默认提供的RouteDefinitionRouteLocator

RouteDefinitionRouteLocator

bean注册

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java
/*
 * RouteLocator是用来获取路由的,RouteDefinitionRouteLocator只是其中一个实现类,
 * 封装了RouteDefinitionLocator、GatewayFilterFactory、RoutePredicateFactory,
 * 可以看到其组合了RouteDefinitionLocator,实际上是上面的CompositeRouteDefinitionLocator类型的对象。
 * RouteDefinitionRouteLocator是指根据RouteDefinition来获取Route对象。
 */
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
        List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> predicates,
        RouteDefinitionLocator routeDefinitionLocator, ConfigurationService configurationService) {
    return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, gatewayFilters, properties,
            configurationService);
}

这里设置了多个参数,其中最最重要的是RouteDefinitionLocator。同样的套路,该对象实际上是一个包装对象,封装了其他多个RouteDefinitionLocator

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java
/*
 * 该bean会封装所有的RouteDefinitionLocator,这里的bean的优先级更高。
 * 会将上面的propertiesRouteDefinitionLocator和InMemoryRouteDefinitionRepository进行封装。
 */
@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {
    // 默认情况下,这里的routeDefinitionLocators包含2个元素,分别是上面两个bean定义。
    return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
}
/*
 * 要注意区分RouteDefinitionLocator和RouteLocator,前者是存储路由定义信息,后者是查找路由信息
 * 两者的关系参考类图:https://img-blog.csdnimg.cn/20210401131318609.png
 */

/*
 * 存储从配置文件中读取的路由信息
 * 这是个RouteDefinitionLocator
 */
@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
    return new PropertiesRouteDefinitionLocator(properties);
}
// 这个也是个RouteDefinitionLocator
@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
    return new InMemoryRouteDefinitionRepository();
}

其中routeDefinitionLocator方法注册的bean就是实际上被注入RouteDefinitionRouteLocator中的路由定义定位器对象,这也是方法上的@Primary注解的作用。

注意区分路由定位器(RouteLocator),和路由定义定位器(RouteDefinitionLocator)。前者的用途是获取路由信息,而后者是存储了路由信息。所以在自定义的时候,对于”找“,可以自定义路由定位器;对于“存”,可以自定义路由定义定位器。

构造方法

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java
/**
 * Default filters name.
 */
public static final String DEFAULT_FILTERS = "defaultFilters";

protected final Log logger = LogFactory.getLog(getClass());

private final RouteDefinitionLocator routeDefinitionLocator;

private final ConfigurationService configurationService;

private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>();

private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>();

private final GatewayProperties gatewayProperties;
public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,
        List<RoutePredicateFactory> predicates, List<GatewayFilterFactory> gatewayFilterFactories,
        GatewayProperties gatewayProperties, ConfigurationService configurationService) {
    this.routeDefinitionLocator = routeDefinitionLocator;
    this.configurationService = configurationService;
    // 初始化断言信息
    initFactories(predicates);
    // 初始化filter信息,与初始化断言信息类似
    gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));
    this.gatewayProperties = gatewayProperties;
}

初始化断言工厂

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java
private void initFactories(List<RoutePredicateFactory> predicates) {
    predicates.forEach(factory -> {
        // 这里key是RoutePredicateFactory实现类的类名称的前缀,如AfterRoutePredicateFactory则key为After
        String key = factory.name();
        if (this.predicates.containsKey(key)) {
            this.logger.warn("A RoutePredicateFactory named " + key + " already exists, class: "
                    + this.predicates.get(key) + ". It will be overwritten.");
        }
        /*
         * 如果已经存在了同一个key的predicateFactory,则覆盖掉,
         * 因为该方法只有在RouteDefinitionRouterLocator组件被初始化时才会被调用, 所以这里说明以SCG内置的断言工厂为主
         */
        this.predicates.put(key, factory);
        if (logger.isInfoEnabled()) {
            logger.info("Loaded RoutePredicateFactory [" + key + "]");
        }
    });
}

这里主要是在对重复设置的断言工厂做判断。

获取路由

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java
// 返回路由信息
@Override
public Flux<Route> getRoutes() {
    // 通过routeDefinitionLocator获取所有的路由定义信息
    return getRoutes(this.routeDefinitionLocator.getRouteDefinitions());
}
private Flux<Route> getRoutes(Flux<RouteDefinition> routeDefinitions) {
    // 将路由信息转为路由对象
    Flux<Route> routes = routeDefinitions.map(this::convertToRoute);

    if (!gatewayProperties.isFailOnRouteDefinitionError()) {
        // instead of letting error bubble up, continue
        routes = routes.onErrorContinue((error, obj) -> {
            if (logger.isWarnEnabled()) {
                logger.warn("RouteDefinition id " + ((RouteDefinition) obj).getId()
                        + " will be ignored. Definition has invalid configs, " + error.getMessage());
            }
        });
    }

    return routes.map(route -> {
        if (logger.isDebugEnabled()) {
            logger.debug("RouteDefinition matched: " + route.getId());
        }
        return route;
    });
}
private Route convertToRoute(RouteDefinition routeDefinition) {
    // 获取路由定义中的断言信息并组合成一个断言(如果有多个的话)
    AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
    // 获取路由定义中的filter
    List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);
    // 构建Route对象
    return Route.async(routeDefinition).asyncPredicate(predicate).replaceFilters(gatewayFilters).build();
}

可以看到,这里是通过路由定义定位器来获取的路由信息,对于每条路由信息,会组合它的多条断言,获取该路由能够使用的过滤器,最后构建Route对象,可以说主要的实现在于convertToRoute方法。

组合断言

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java
private AsyncPredicate<ServerWebExchange> combinePredicates(RouteDefinition routeDefinition) {
    List<PredicateDefinition> predicates = routeDefinition.getPredicates();
    if (predicates == null || predicates.isEmpty()) {
        // this is a very rare case, but possible, just match all
        return AsyncPredicate.from(exchange -> true);
    }
    AsyncPredicate<ServerWebExchange> predicate = lookup(routeDefinition, predicates.get(0));

    /*
     * 如果该路由定义了多个断言,将其用and运算连接起来
     */
    for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) {
        // 转化为异步的断言
        AsyncPredicate<ServerWebExchange> found = lookup(routeDefinition, andPredicate);
        // 用and连接起来
        predicate = predicate.and(found);
    }

    return predicate;
}

如果一个路由定义有多个断言的话,那么它们之间是“与”的关系,即必须都满足。

获取过滤器

getFilters
loadGatewayFilters
<
>
java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java
private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) {
    List<GatewayFilter> filters = new ArrayList<>();

    // TODO: support option to apply defaults after route specific filters?
    // 如果配置了全局默认过滤器,则添加到集合中
    if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {
        filters.addAll(loadGatewayFilters(routeDefinition.getId(),
                new ArrayList<>(this.gatewayProperties.getDefaultFilters())));
    }

    // 添加路由定义中配置的过滤器
    final List<FilterDefinition> definitionFilters = routeDefinition.getFilters();
    if (!CollectionUtils.isEmpty(definitionFilters)) {
        filters.addAll(loadGatewayFilters(routeDefinition.getId(), definitionFilters));
    }

    // 对过滤器进行排序
    AnnotationAwareOrderComparator.sort(filters);
    return filters;
}
java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java
@SuppressWarnings("unchecked")
List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) {
    ArrayList<GatewayFilter> ordered = new ArrayList<>(filterDefinitions.size());
    // 遍历过滤器定义
    for (int i = 0; i < filterDefinitions.size(); i++) {
        // 获取过滤器定义
        FilterDefinition definition = filterDefinitions.get(i);
        // 根据过滤器名称获取filter工厂
        GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());
        if (factory == null) {
            throw new IllegalArgumentException(
                    "Unable to find GatewayFilterFactory with name " + definition.getName());
        }
        if (logger.isDebugEnabled()) {
            logger.debug("RouteDefinition " + id + " applying filter " + definition.getArgs() + " to "
                    + definition.getName());
        }

        // @formatter:off
        // 根据filter的定义,利用filter工厂生成配置对象
        Object configuration = this.configurationService.with(factory)
                .name(definition.getName())
                .properties(definition.getArgs())
                .eventFunction((bound, properties) -> new FilterArgsEvent(
                        // TODO: why explicit cast needed or java compile fails
                        RouteDefinitionRouteLocator.this, id, (Map<String, Object>) properties))
                .bind();
        // @formatter:on

        // some filters require routeId
        // TODO: is there a better place to apply this?
        if (configuration instanceof HasRouteId) {
            HasRouteId hasRouteId = (HasRouteId) configuration;
            // 设置路由id
            hasRouteId.setRouteId(id);
        }

        // 生成GatewayFilter
        GatewayFilter gatewayFilter = factory.apply(configuration);
        if (gatewayFilter instanceof Ordered) {
            ordered.add(gatewayFilter);
        }
        else { // 如果没有实现Ordered接口,则封装为OrderedGatewayFilter
            ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));
        }
    }

    return ordered;
}

总之,路由的获取最终还是要依靠路由定义定位器。

CompositeRouteDefinitionLocator

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteDefinitionLocator.java
public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator {

	private static final Log log = LogFactory.getLog(CompositeRouteDefinitionLocator.class);

	private final Flux<RouteDefinitionLocator> delegates;

	private final IdGenerator idGenerator;

	public CompositeRouteDefinitionLocator(Flux<RouteDefinitionLocator> delegates) {
		this(delegates, new AlternativeJdkIdGenerator());
	}

	public CompositeRouteDefinitionLocator(Flux<RouteDefinitionLocator> delegates, IdGenerator idGenerator) {
		this.delegates = delegates;
		this.idGenerator = idGenerator;
	}

	@Override
	public Flux<RouteDefinition> getRouteDefinitions() {
		// 通过委托对象来获取路由定义
		return this.delegates.flatMapSequential(RouteDefinitionLocator::getRouteDefinitions)
				.flatMap(routeDefinition -> { // 处理返回的多条路由定义
					if (routeDefinition.getId() == null) {
						// 如果路由id是null,则生成一个
						return randomId().map(id -> {
							routeDefinition.setId(id);
							if (log.isDebugEnabled()) {
								log.debug("Id set on route definition: " + routeDefinition);
							}
							return routeDefinition;
						});
					}
					return Mono.just(routeDefinition);
				});
	}

	protected Mono<String> randomId() {
		return Mono.fromSupplier(idGenerator::generateId).map(UUID::toString).publishOn(Schedulers.boundedElastic());
	}

}

CompositeRouteLocator类似,这里主要是从实际的路由定义定位器来获取路由定义。

PropertiesRouteDefinitionLocator

我们可以在配置文件中配置路由定义,比如:

yaml
spring:  
  cloud:  
    gateway:  
      routes:  
        - id: path_route  
          uri: http://httpbin.org:80  
          predicates:  
            - Path=/get  
        - id: host_route  
          uri: http://httpbin.org:80  
          predicates:  
            - Host=*.myhost.org

最终,这些配置会被封装为GatewayProperties对象,而上面配置中的路由信息会被PropertiesRouteDefinitionLocator返回。

PropertiesRouteDefinitionLocator
GatewayProperties
<
>
java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteDefinitionLocator.java
public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {

	private final GatewayProperties properties;

	public PropertiesRouteDefinitionLocator(GatewayProperties properties) {
		this.properties = properties;
	}

	@Override
	public Flux<RouteDefinition> getRouteDefinitions() {
		return Flux.fromIterable(this.properties.getRoutes());
	}

}
java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java
@ConfigurationProperties(GatewayProperties.PREFIX)
@Validated
public class GatewayProperties {

	/**
	 * Properties prefix.
	 */
	public static final String PREFIX = "spring.cloud.gateway";

	private final Log logger = LogFactory.getLog(getClass());

	/**
	 * List of Routes.
	 */
	// 路由信息
	@NotNull
	@Valid
	private List<RouteDefinition> routes = new ArrayList<>();

	/**
	 * List of filter definitions that are applied to every route.
	 *
	 */
	// 这个列表里的过滤器会作用在每个路由,效果类似GlobalFilter
	private List<FilterDefinition> defaultFilters = new ArrayList<>();

	private List<MediaType> streamingMediaTypes = Arrays.asList(MediaType.TEXT_EVENT_STREAM,
			new MediaType("application", "stream+json"), new MediaType("application", "grpc"),
			new MediaType("application", "grpc+protobuf"), new MediaType("application", "grpc+json"));

	/**
	 * Option to fail on route definition errors, defaults to true. Otherwise, a warning
	 * is logged.
	 */
	private boolean failOnRouteDefinitionError = true;

	public List<RouteDefinition> getRoutes() {
		return routes;
	}

	public void setRoutes(List<RouteDefinition> routes) {
		this.routes = routes;
		if (routes != null && routes.size() > 0 && logger.isDebugEnabled()) {
			logger.debug("Routes supplied from Gateway Properties: " + routes);
		}
	}

	public List<FilterDefinition> getDefaultFilters() {
		return defaultFilters;
	}

	public void setDefaultFilters(List<FilterDefinition> defaultFilters) {
		this.defaultFilters = defaultFilters;
	}

	public List<MediaType> getStreamingMediaTypes() {
		return streamingMediaTypes;
	}

	public void setStreamingMediaTypes(List<MediaType> streamingMediaTypes) {
		this.streamingMediaTypes = streamingMediaTypes;
	}

	public boolean isFailOnRouteDefinitionError() {
		return failOnRouteDefinitionError;
	}

	public void setFailOnRouteDefinitionError(boolean failOnRouteDefinitionError) {
		this.failOnRouteDefinitionError = failOnRouteDefinitionError;
	}

	@Override
	public String toString() {
		return new ToStringCreator(this).append("routes", routes).append("defaultFilters", defaultFilters)
				.append("streamingMediaTypes", streamingMediaTypes)
				.append("failOnRouteDefinitionError", failOnRouteDefinitionError).toString();

	}

}

InMemoryRouteDefinitionRepository

另外还有一种用得不是很多的路由定义定位器:InMemoryRouteDefinitionRepository。这种定位器可以在运行时对路由信息进行增删。

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/InMemoryRouteDefinitionRepository.java
public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {

	private final Map<String, RouteDefinition> routes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());

	@Override
	public Mono<Void> save(Mono<RouteDefinition> route) {
		return route.flatMap(r -> {
			if (ObjectUtils.isEmpty(r.getId())) {
				return Mono.error(new IllegalArgumentException("id may not be empty"));
			}
			routes.put(r.getId(), r);
			return Mono.empty();
		});
	}

	@Override
	public Mono<Void> delete(Mono<String> routeId) {
		return routeId.flatMap(id -> {
			if (routes.containsKey(id)) {
				routes.remove(id);
				return Mono.empty();
			}
			return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
		});
	}

	@Override
	public Flux<RouteDefinition> getRouteDefinitions() {
		Map<String, RouteDefinition> routesSafeCopy = new LinkedHashMap<>(routes);
		return Flux.fromIterable(routesSafeCopy.values());
	}

}

但是这里的修改只是对该类型对象中的routes属性进行修改,而没有修改上面CachingRouteLocator中缓存起来的路由信息。那么需要一种机制让它知道应该要刷新路由信息,实际上CachingRouteLocator确实实现了事件机制来进行路由刷新。

刷新路由

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteLocator.java
@Override
public void onApplicationEvent(RefreshRoutesEvent event) {
    try {
        // 如果是scoped的情况
        if (this.cache.containsKey(CACHE_KEY) && event.isScoped()) {
            // 调用重载的fetch方法来获取满足元数据的路由信息
            final Mono<List<Route>> scopedRoutes = fetch(event.getMetadata()).collect(Collectors.toList())
                    .onErrorResume(s -> Mono.just(List.of()));

            scopedRoutes.subscribe(scopedRoutesList -> {
                // 这里为什么要合并非scoped路由?这样岂不是将满足与不满足的都获取到了吗?那这里的scoped还有什么意义?
                Flux.concat(Flux.fromIterable(scopedRoutesList), getNonScopedRoutes(event)).materialize()
                        .collect(Collectors.toList()).subscribe(signals -> {
                            // 发布刷新路由结果事件,表示路由已刷新
                            applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this));
                            // 修改cache
                            cache.put(CACHE_KEY, signals);
                        }, this::handleRefreshError);
            }, this::handleRefreshError);
        }
        else {
            // 重新获取所有路由
            final Mono<List<Route>> allRoutes = fetch().collect(Collectors.toList());

            allRoutes.subscribe(list -> Flux.fromIterable(list).materialize().collect(Collectors.toList())
                    .subscribe(signals -> {
                        applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this));
                        // 重新设置路由信息
                        cache.put(CACHE_KEY, signals);
                    }, this::handleRefreshError), this::handleRefreshError);
        }
    }
    catch (Throwable e) {
        handleRefreshError(e);
    }
}
private Flux<Route> getNonScopedRoutes(RefreshRoutesEvent scopedEvent) {
    return this.getRoutes() // 获取路由
            // 筛选元数据不满足的那些路由
            .filter(route -> !RouteLocator.matchMetadata(route.getMetadata(), scopedEvent.getMetadata()));
}

这里分为了事件是全局刷新还是局部刷新两种情况,全局刷新很好理解,注解调用无参的fetch方法重新获取路由信息。 局部刷新要根据事件对象中的元数据进行筛选,调用有参的fetch方法。

CachingRouteLocator
RouteDefinitionRouteLocator
RouteLocator
<
>
java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteLocator.java
private Flux<Route> fetch(Map<String, Object> metadata) {
    return this.delegate.getRoutesByMetadata(metadata).sort(AnnotationAwareOrderComparator.INSTANCE);
}
java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java
@Override
public Flux<Route> getRoutesByMetadata(Map<String, Object> metadata) {
    return getRoutes(this.routeDefinitionLocator.getRouteDefinitions()
            .filter(routeDef -> RouteLocator.matchMetadata(routeDef.getMetadata(), metadata)));
}
java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteLocator.java
static boolean matchMetadata(Map<String, Object> toCheck, Map<String, Object> expectedMetadata) {
    if (CollectionUtils.isEmpty(expectedMetadata)) {
        return true;
    }
    else {
        return toCheck != null
                && expectedMetadata.entrySet().stream().allMatch(keyValue -> toCheck.containsKey(keyValue.getKey())
                        && toCheck.get(keyValue.getKey()).equals(keyValue.getValue()));
    }
}

RouteDefinitionRouteLocator的方法中,重新从路由定义定位器中获取路由,然后进行过滤筛选。在RouteLocator的匹配方法中,对两个map进行比较,要求key都存在,且value要相等。

再回到CachingRouteLocator中的onApplicationEvent方法,获取到了满足元数据的路由后,调用了getNonScopedRoutes方法来获取不满足元数据的路由,然后将满足与不满足的合并在一起。

目前确实没有搞清楚知道这里的操作意义,按理说只保留满足元数据的路由,否则事件是不是scoped的又有什么意义呢?(等搞清楚后再做补充)

自定义路由定位器

在Spring Cloud Gateway中,是可以自定义路由定位器的,这些路由定位器对象会被封装进入CompositeRouteLocator对象中。

RouteLocatorBuilder

为了方便自定义路由定位器,Spring Cloud Gateway定义和注入了RouteLocatorBuilder类型的bean。

java
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java
/*
 * 用于构建RouteLocator,方便创建路由定位器。
 */
@Bean
public RouteLocatorBuilder routeLocatorBuilder(ConfigurableApplicationContext context) {
    return new RouteLocatorBuilder(context);
}

然后可以使用类似下面的代码配置。

java
@Configuration  
public class GatewayConfig {
    @Bean  
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {  
       return builder.routes()  
             .route("path_route", r -> r.path("/get")  
                   .uri("http://httpbin.org:80"))  
             .route("host_route", r -> r.host("*.myhost.org")  
                   .uri("http://httpbin.org:80"))  
             .build();  
    }  
}

RouteLocatorBuilderroutes方法返回一个Builder(它的内部类)对象,然后通过每个route方法来定义路由,最后的build方法用来构建。route方法的第一个参数是路由id,第二个参数是一个PredicateSpec类型的对象,可以通过它定义的方法来设置断言,比如上面的pathhosturi方法。