文章背景图

Spring-Cloud-Gateway 源码解析 —— 路由(2.2)之 RouteDefinitionRouteLocator 路由配置

2026-01-30
5
-
- 分钟
|

摘要: 原创出处 http://www.iocoder.cn/Spring-Cloud-Gateway/route-locator-route-definition/ 「芋道源码」欢迎转载,保留摘要,谢谢!

本文主要基于 Spring-Cloud-Gateway 2.0.X M4

阅读源码最好的方式,是使用 IDEA 进行调试 Spring Cloud Gateway 源码,不然会一脸懵逼。

1. 概述

本文主要分享 RouteDefinitionRouteLocator 的源码实现

01.jpeg
  • 蓝色部分 :RouteDefinitionRouteLocator 。


推荐 Spring Cloud 书籍

2. RouteDefinitionRouteLocator

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator ,基于 RouteDefinitionLocator 的 RouteLocator 实现类

RouteDefinitionRouteLocator 从 RouteDefinitionLocator 获取 RouteDefinition ,转换成 Route 。如下图 :

02-RPytuHtCKaEfyoNiaHtcwDobuEcHmidr.png

2.1 构造方法

RouteDefinitionRouteLocator 构造方法,代码如下 :

public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAware {
	protected final Log logger = LogFactory.getLog(getClass());

	private final RouteDefinitionLocator routeDefinitionLocator;
	/**
	 * RoutePredicateFactory 映射
	 * key :{@link RoutePredicateFactory#name()}
	 */
	private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>();
	/**
	 * GatewayFilterFactory 映射
	 * key :{@link GatewayFilterFactory#name()}
	 */
	private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>();
	private final GatewayProperties gatewayProperties;
	private final SpelExpressionParser parser = new SpelExpressionParser();
	private BeanFactory beanFactory;

	public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,
									   List<RoutePredicateFactory> predicates,
									   List<GatewayFilterFactory> gatewayFilterFactories,
									   GatewayProperties gatewayProperties) {
		// 设置 RouteDefinitionLocator
		this.routeDefinitionLocator = routeDefinitionLocator;
		// 初始化 RoutePredicateFactory
		initFactories(predicates);
		// 初始化 RoutePredicateFactory
		gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));
		// 设置 GatewayProperties
		this.gatewayProperties = gatewayProperties;
	}

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}
}

  • routeDefinitionLocator 属性,提供 RouteDefinition 的 RouteDefinitionLocator 。

  • predicates 属性,RoutePredicateFactory Bean 对象映射

    • key{@link RoutePredicateFactory#name()}

    • 通过它,将 RouteDefinition.predicates 转换成 Route.predicates

    • 第 26 行 :调用 #initFactories() 方法,初始化映射。逻辑比较简单,点击 链接 查看代码。

  • gatewayFilterFactories 属性,RoutePredicateFactory Bean 对象映射

    • key{@link GatewayFilterFactory#name()}

    • 通过它,将 RouteDefinition.filters 转换成 Route.filters

    • 第 28 行 :初始化映射。

  • gatewayProperties 属性,使用 GatewayProperties.defaultFilters 默认过滤器定义数组,添加到每个 Route 。下文会看到相关代码的实现。

  • parser 属性,Spring EL 表达式解析器。在 「2.4 获得 Tuple」 会看到它的使用。

  • beanFactory 属性,Bean 工厂。

2.2 获得 Route

#getRoutes() 方法,获得 Route 数组。代码如下 :

@Override
public Flux<Route> getRoutes() {
	return this.routeDefinitionLocator.getRouteDefinitions()
			.map(this::convertToRoute) // RouteDefinition => Route
			//TODO: error handling
			.map(route -> { // 打印日志
				if (logger.isDebugEnabled()) {
					logger.debug("RouteDefinition matched: " + route.getId());
				}
				return route;
			});

}

  • 第 3 行 : 调用 RouteDefinitionLocator#getRouteDefinitions() 方法,获得 RouteDefinitions 数组。

  • 第 4 行 :调用 #convertToRoute() 方法,将每个 RouteDefinition 转换成 Route 。该方法在 「2.3 转换 Route」 详细解析。

  • 第 7 至 11 行 :打印输出每个 Route 。

2.3 转换 Route

#convertToRoute() 方法,将每个 RouteDefinition 转换成 Route 。代码如下 :

private Route convertToRoute(RouteDefinition routeDefinition) {
    // 合并 Predicate
	Predicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
	// 获得 GatewayFilter
	List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);
	// 构建 Route
	return Route.builder(routeDefinition)
			.predicate(predicate)
			.gatewayFilters(gatewayFilters)
			.build();
}

  • 第 3 行 :调用 #combinePredicates() 方法,将 RouteDefinition.predicates 数组合并成一个 java.util.function.Predicate ,这样 RoutePredicateHandlerMapping 为请求匹配 Route ,只要调用一次 Predicate#test(ServerWebExchange) 方法即可。

  • 第 5 行 :调用 #getFilters() 方法,获得 GatewayFilter 数组

  • 第 7 至 10 行 :构建 Route 。


#combinePredicates() 方法,代码如下 :

private Predicate<ServerWebExchange> combinePredicates(RouteDefinition routeDefinition) {
    // 寻找 Predicate
	List<PredicateDefinition> predicates = routeDefinition.getPredicates();
	Predicate<ServerWebExchange> predicate = lookup(routeDefinition, predicates.get(0));
	// 拼接 Predicate
	for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) {
		Predicate<ServerWebExchange> found = lookup(routeDefinition, andPredicate);
		predicate = predicate.and(found);
	}
	// 返回 Predicate
	return predicate;
}

private Predicate<ServerWebExchange> lookup(RouteDefinition routeDefinition, PredicateDefinition predicate) {
    // 获得 RoutePredicateFactory
	RoutePredicateFactory found = this.predicates.get(predicate.getName());
	if (found == null) {
		throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName());
	}
	// 获得 Tuple
	Map<String, String> args = predicate.getArgs();
	if (logger.isDebugEnabled()) {
		logger.debug("RouteDefinition " + routeDefinition.getId() + " applying "
				+ args + " to " + predicate.getName());
	}
	Tuple tuple = getTuple(found, args, this.parser, this.beanFactory);
	// 获得 Predicate
	return found.apply(tuple);
}

  • 第 2 至 9 行 :通过调用 #lookup() 方法,查找 PredicateDefinition 对应的 Predicate 。为什么拆成两部分?第一部分找到 java.util.function.Predicate ,第二部分通过 Predicate#and(Predicate) 方法不断拼接。

  • 第 11 行 :返回 Predicate 。

  • ---------------------------- 分割线 --------------------------

  • 第 14 至 29 行 :#lookup() 方法。


#getFilters() 方法,代码如下 :

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("defaultFilters",
				this.gatewayProperties.getDefaultFilters()));
	}
	// 添加 配置的过滤器
	if (!routeDefinition.getFilters().isEmpty()) {
		filters.addAll(loadGatewayFilters(routeDefinition.getId(), routeDefinition.getFilters()));
	}
	// 排序
	AnnotationAwareOrderComparator.sort(filters);
	return filters;
}

private List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) {
	List<GatewayFilter> filters = filterDefinitions.stream()
			.map(definition -> { // FilterDefinition => GatewayFilter
			    // 获得 GatewayFilterFactory
				GatewayFilterFactory filter = this.gatewayFilterFactories.get(definition.getName());
				if (filter == null) {
					throw new IllegalArgumentException("Unable to find GatewayFilterFactory with name " + definition.getName());
				}
				// 获得 Tuple
				Map<String, String> args = definition.getArgs();
				if (logger.isDebugEnabled()) {
					logger.debug("RouteDefinition " + id + " applying filter " + args + " to " + definition.getName());
				}
				Tuple tuple = getTuple(filter, args, this.parser, this.beanFactory);
				// 获得 GatewayFilter
				return filter.apply(tuple);
			})
			.collect(Collectors.toList()); // 转成 List
   // GatewayFilter => OrderedGatewayFilter
	ArrayList<GatewayFilter> ordered = new ArrayList<>(filters.size());
	for (int i = 0; i < filters.size(); i++) {
		ordered.add(new OrderedGatewayFilter(filters.get(i), i+1));
	}
	// 返回 GatewayFilter 数组
	return ordered;
}

  • 第 4 至 8 行 :调用 #loadGatewayFilters() 方法,使用 GatewayProperties.defaultFilters 默认的过滤器配置,将 FilterDefinition 转换成 GatewayFilter 。

  • 第 10 至 12 行 :调用 #loadGatewayFilters() 方法,使用 RouteDefinition.filters 配置的过滤器配置,将 FilterDefinition 转换成 GatewayFilter 。

  • ---------------------------- 分割线 --------------------------

  • 第 18 至 43 行 :#loadGatewayFilters() 方法。

    • 第 20 至 34 行 :将 FilterDefinition 转换成 GatewayFilter 。

      • 第 21 至 25 行 :获得 GatewayFilterFactory Bean 对象。

      • 第 27 至 31 行 :调用 #getTuple() 方法,获得 Tuple 。该方法比较复杂,在 「2.4 获得 Tuple」 详细解析。

      • 第 33 行 :创建 GatewayFilter 。

    • 第 35 行 :获得 GatewayFilter 数组。

  • 第 37 至 40 行 :将 GatewayFilter 数组转换成 OrderedGatewayFilter 数组。在 《Spring-Cloud-Gateway 源码解析 —— 过滤器 (4.1) 之 GatewayFilter 一览》 详细解析。

  • 第 42 行 :返回 GatewayFilter 数组

2.4 获得 Tuple

在看 #getTuple() 方法的代码实现之前,我们先了解下 Tuple 。

Tuple ,定义如下 :

FROM 《简单实现 Java 的 Tuple 元组数据类型》
元组类型,即 Tuple 常在脚本语言中出现,例如 Scala 的 ("Unmi", "[email protected]", "blahbla")
元组可认为是象数组一样的容器,它的目的是让你方便构造和引用,例如 Pair 可认为是一个只能存两个元素的元组,像是个 Map
真正的元组应该是可以任意多个元素的容器,绕来绕去,它还是数组,或列表,所以我们实现上还是要借助于数组或是列表。

截止目前,Java 并未内置 Tuple 的实现。Spring 提供了 spring-tuple 类库,提供了 Tuple 的支持。使用示例如下 :

// 1 对
Tuple tuple = tuple().of("foo", "bar");
// 2 对
Tuple tuple2 = tuple().of("up", 1, "down", 2);
// 3 对
Tuple tuple3 = tuple().of("up", 1, "down", 2, "charm", 3 );
// 4 对
Tuple tuple4 = tuple().of("up", 1, "down", 2, "charm", 3, "strange", 4);
// 6 对 ( 适用于超过 4 对 )
Tuple tuple6 = tuple().put("up", 1)
                      .put("down", 2)
        		      .put("charm", 3)
        		      .put("strange", 4)
        		      .put("bottom", 5)
        		      .put("top", 6)
        		      .build();


那么为什么 RoutePredicateFactory#apply(Tuple) / GatewayFilterFactory#apply(Tuple) 需要使用 Tuple 呢 ?RoutePredicateFactory / GatewayFilterFactory 子类实现类需要成对的参数不同,例如 :

  • org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory ,使用 status 一对参数。

  • org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory ,使用 name / value 两对参数。


OK ,我们开始看看 #getTuple() 方法,代码如下 :

/* for testing */ static Tuple getTuple(ArgumentHints hasArguments, Map<String, String> args, SpelExpressionParser parser, BeanFactory beanFactory) {
	TupleBuilder builder = TupleBuilder.tuple();

	// 参数为空
	List<String> argNames = hasArguments.argNames();
	if (!argNames.isEmpty()) {
		// ensure size is the same for key replacement later
		if (hasArguments.validateArgs() && args.size() != argNames.size()) {
			throw new IllegalArgumentException("Wrong number of arguments. Expected " + argNames
					+ " " + argNames + ". Found " + args.size() + " " + args + "'");
		}
	}

	// 创建 Tuple
	int entryIdx = 0;
	for (Map.Entry<String, String> entry : args.entrySet()) {
	    // 获得参数 KEY
		String key = entry.getKey();
		// RoutePredicateFactory has name hints and this has a fake key name
		// replace with the matching key hint
		if (key.startsWith(NameUtils.GENERATED_NAME_PREFIX) && !argNames.isEmpty()
				&& entryIdx < args.size()) {
			key = argNames.get(entryIdx);
		}
		// 获得参数 VALUE
		Object value;
		String rawValue = entry.getValue();
		if (rawValue != null) {
			rawValue = rawValue.trim();
		}
		if (rawValue != null && rawValue.startsWith("#{") && entry.getValue().endsWith("}")) {
			// assume it's spel
			StandardEvaluationContext context = new StandardEvaluationContext();
			context.setBeanResolver(new BeanFactoryResolver(beanFactory));
			Expression expression = parser.parseExpression(entry.getValue(), new TemplateParserContext());
			value = expression.getValue(context);
		} else {
			value = entry.getValue();
		}
		// 添加 KEY / VALUE
		builder.put(key, value);
		entryIdx++;
	}
	Tuple tuple = builder.build();

	// 校验参数
	if (hasArguments.validateArgs()) {
		for (String name : argNames) {
			if (!tuple.hasFieldName(name)) {
				throw new IllegalArgumentException("Missing argument '" + name + "'. Given " + tuple);
			}
		}
	}
	return tuple;
}

  • hasArguments 参数,点击 org.springframework.cloud.gateway.support.ArgumentHints 查看代码实现。RoutePredicateFactory / GatewayFilterFactory 实现 ArgumentHints 接口

  • 第 5 至 12 行 :校验参数是否正确( 需要参数非空 )。

  • 第 15 至 44 行 :创建 Tuple 。

    • 第 18 至 24 行 :获得一对参数 KEY 。

    • 第 26 至 39 行 :获得一对参数 VALUE 。

    • 第 41 行 :添加一对参数 KEY / VALUE 。我们在此处打断点,看看此时各变量的值,路由配置如下 :

      spring:
        cloud:
          gateway:
            routes:
            - id: websocket_test
              uri: ws://localhost:9000
              order: 9000
              predicates:
              - Path=/echo
              - Query=foo, ba.

      • PATH

        • /echo

          03-mWgZAdOfomCAtBqLXcjVJfRRTLdyxLkd.png
      • Query

        • foo

          05-UZhwCdXXCZRkqlVMgbUtQOvSCVywpljM.png
        • ba.

    • 第 44 行 :创建 Tuple 。

  • 第 47 至 53 行 :校验参数是否正确( 需要参数都存在 )。

  • 第 54 行 :返回 Tuple 。

评论交流

文章目录