Gateway(二)Route

2020年4月18日 | 作者 Siran | 4600字 | 阅读大约需要10分钟
归档于 gateway | 标签 #gateway

1.简述

在之前 Gateway(一)初始化 文章中,已经把Gateway初始化阐述完了,接下来就是分析我们平时在Properties或者yml中配置的有关Gateway的配置是如何构建成Route的。


2.Route 构建方式

一般构建分为两种外部化配置和编程方式,例子都来自与官方。

外部化配置

spring:
  cloud:
    gateway:
      routes: 
      - id: after_route //①
        uri: https://example.org //②
        predicates: 
        - Cookie=mycookie,mycookievalue//③
        filters: 
        - AddRequestHeader=X-Request-Foo, Bar//④
  • ① 配置了一个Route id 为 after_route
  • ② 客户端请求转发的目的地:https://example.org
  • ③ 在request 中 当存在名字 mycookie 的cookie的值匹配 mycookievalue 则算成功
  • ④ 定义了一个Filter,匹配成功后,会在请求头上添加 X-Request-Foo:Bar

编程方式

用上面的例子,转换成编程方式

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
   builder.routes()
				.route(r -> r.cookie("mycookie","mycookievalue")
						.filters(f -> f.addRequestHeader("X-Request-Foo","Bar"))
						.uri("https://example.org")
				)
				.build();
}

简单介绍了构建Route的方式,下面分析Route是如何把外部化配置与编码配置之间进行转换。


3.Route 构建原理

在上面说了Route 构建有两种一种是外部配置方式还有一种是编程方式

3.1 外部配置方式:

外部配置是通过 GatewayProperties 进行构建的,具体怎么构建参考我的这篇文章:

Spring Boot 注解 @ConfigurationProperties

public class GatewayProperties {

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

	/**
	 * List of Routes.
	 */
	@NotNull
	@Valid
	private List<RouteDefinition> routes = new ArrayList<>();//① 用来对 Route 进行定义。

	/**xxx
	 * List of filter definitions that are applied to every route.
	 * ② 用于定义默认的 Filter 列表,默认的 Filter 会应用到每一个 Route 上,gateway 处理时会将其与 Route 中指定的 Filter 进行合并后并逐个执行。
	 */
	private List<FilterDefinition> defaultFilters = new ArrayList<>();
	}
  • ① RouteDefinition : 用来对 Route 进行定义。也就是,通过 GatewayProperties 会与外部化配置进行绑定,把外部化配置比如properties 或者 yml 绑定到 GatewayProperties 中。

    public class RouteDefinition {
        @NotEmpty
        private String id = UUID.randomUUID().toString(); // ①
        @NotEmpty
        @Valid
        private List<PredicateDefinition> predicates = new ArrayList<>();  // ②
      
        @Valid
        private List<FilterDefinition> filters = new ArrayList<>();  // ③
      
        @NotNull
        private URI uri;  // ④
      
        private int order = 0; // ⑤
    }
    
    • 定义 Route 的 id,默认使用 UUID。

    • 定义 Predicate。

    • 定义 Filter。

    • 定义目的地 URI。

    • 定义 Route 的序号。

  • ② FilterDefinition :同上,默认的 filter,会作用到每一个 Route上。

    public class FilterDefinition {
        @NotNull
        private String name; //①
        private Map<String, String> args = new LinkedHashMap<>();//②
    
    • ① filter 的名字
    • ② args 属性,参数数组。例如,name=AddRequestParameter / args={"_genkey_0": "foo", "_genkey_1": "bar"} ,添加请求参数 foobar
  • ③ PredicateDefinition

    public class PredicateDefinition {
        @NotNull
        private String name; // ①
        private Map<String, String> args = new LinkedHashMap<>(); // ②
    
    • Predicate 的名称
    • 一个 Map 类型的参数,构造 Predicate 使用到的键值对参数。

3.1.1 RouteDefinitionLocator

Gateway(一)初始化 文章中的GatewayAutoConfiguration 核心配置类中,有提到此类。

@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(
			List<RouteDefinitionLocator> routeDefinitionLocators) {
		return new CompositeRouteDefinitionLocator(
				Flux.fromIterable(routeDefinitionLocators));
	}

Gateway 提供多种方式来获取外部的配置,而RouteDefinitionLocator 就是父接口,下面有多种实现。

  • PropertiesRouteDefinitionLocator:从配置文件( 例如,YML / Properties 等 ) 读取路由配置。代码如下 :

    public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {
      
      private final GatewayProperties properties;
      
      public PropertiesRouteDefinitionLocator(GatewayProperties properties) {
          this.properties = properties;
      }
      
      @Override
      public Flux<RouteDefinition> getRouteDefinitions() {
          //从 GatewayProperties 获取路由配置数组。
          return Flux.fromIterable(this.properties.getRoutes());
      }
    }
    

    GatewayProperties 如何构建的,参考我的这篇文章:

    Spring Boot 注解 @ConfigurationProperties

  • InMemoryRouteDefinitionRepository:从存储器( 例如,内存 / Redis / MySQL 等 )读取、保存、删除路由配置。InMemoryRouteDefinitionRepository 是基于内存的。

    public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
      
      /**
       * 路由配置映射 通过此来保存route
       * key :路由编号 {@link RouteDefinition#id}
       */
      private final Map<String, RouteDefinition> routes = synchronizedMap(
              new LinkedHashMap<String, RouteDefinition>());
      
      @Override
      public Mono<Void> save(Mono<RouteDefinition> route) {
          return route.flatMap(r -> {
              if (StringUtils.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() {
          return Flux.fromIterable(routes.values());
      }
    }
    

    基于内存,通过Map<String, RouteDefinition> routes 来保存Route,缺点是如果重启,那么route 会丢失。可以实现RouteDefinitionRepository 接口自定义比如通过Redis、Mysql 等来保存Route

  • DiscoveryClientRouteDefinitionLocator:获取在注册中心的服务列表,生成对应的 RouteDefinition 数组。

    public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
      
      private static final Log log = LogFactory
              .getLog(DiscoveryClientRouteDefinitionLocator.class);
      
      private final DiscoveryClient discoveryClient;
      
      private final DiscoveryLocatorProperties properties;
      
      private final String routeIdPrefix;
      
      private final SimpleEvaluationContext evalCtxt;
      
      public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient,
              DiscoveryLocatorProperties properties) {
          this.discoveryClient = discoveryClient;
          this.properties = properties;
          if (StringUtils.hasText(properties.getRouteIdPrefix())) {
              this.routeIdPrefix = properties.getRouteIdPrefix();
          }
          else {
              this.routeIdPrefix = this.discoveryClient.getClass().getSimpleName() + "_";
          }
          evalCtxt = SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods()
                  .build();
      }
      
      @Override
      public Flux<RouteDefinition> getRouteDefinitions() {
      
          SpelExpressionParser parser = new SpelExpressionParser();
          Expression includeExpr = parser
                  .parseExpression(properties.getIncludeExpression());
          Expression urlExpr = parser.parseExpression(properties.getUrlExpression());
      
          Predicate<ServiceInstance> includePredicate;
          if (properties.getIncludeExpression() == null
                  || "true".equalsIgnoreCase(properties.getIncludeExpression())) {
              includePredicate = instance -> true;
          }
          else {
              includePredicate = instance -> {
                  Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class);
                  if (include == null) {
                      return false;
                  }
                  return include;
              };
          }
      
        //① 获取discoveryClient ,然后发起请求
          return Flux.defer(() -> Flux.fromIterable(discoveryClient.getServices()))
                  .map(discoveryClient::getInstances).subscribeOn(Schedulers.elastic())
                  .filter(instances -> !instances.isEmpty())
                  .map(instances -> instances.get(0)).filter(includePredicate)
                  .map(instance -> {
                      String serviceId = instance.getServiceId();
      
                      RouteDefinition routeDefinition = new RouteDefinition();
                      // 设置 ID
                      routeDefinition.setId(this.routeIdPrefix + serviceId);
                      // 设置 uri
                      String uri = urlExpr.getValue(evalCtxt, instance, String.class);
                      routeDefinition.setUri(URI.create(uri));
      
                      final ServiceInstance instanceForEval = new DelegatingServiceInstance(
                              instance, properties);
      
                      //添加 path 断言
                      for (PredicateDefinition original : this.properties.getPredicates()) {
                          PredicateDefinition predicate = new PredicateDefinition();
                          predicate.setName(original.getName());
                          for (Map.Entry<String, String> entry : original.getArgs()
                                  .entrySet()) {
                              String value = getValueFromExpr(evalCtxt, parser,
                                      instanceForEval, entry);
                              predicate.addArg(entry.getKey(), value);
                          }
                          routeDefinition.getPredicates().add(predicate);
                      }
      
                      //添加path 重写过滤器
                      for (FilterDefinition original : this.properties.getFilters()) {
                          FilterDefinition filter = new FilterDefinition();
                          filter.setName(original.getName());
                          for (Map.Entry<String, String> entry : original.getArgs()
                                  .entrySet()) {
                              String value = getValueFromExpr(evalCtxt, parser,
                                      instanceForEval, entry);
                              filter.addArg(entry.getKey(), value);
                          }
                          routeDefinition.getFilters().add(filter);
                      }
      
                      return routeDefinition;
                  });
      }
    }
    

    可以在官方的 GatewaySampleApplication 添加 Eureka 注册中心自行调试:

    @EnableDiscoveryClient // 开启Eureka
    public class GatewaySampleApplication {
          
        // ... 省略其他代码
      
        @Bean
        public RouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient) {
            return new DiscoveryClientRouteDefinitionLocator(discoveryClient);
        }
    }
    

    当然要自己加入Eureka 依赖以及配置文件

  • CachingRouteDefinitionLocator:可以缓存一些Route在本地

    public class CachingRouteDefinitionLocator
          implements RouteDefinitionLocator, ApplicationListener<RefreshRoutesEvent> {
      
      private final RouteDefinitionLocator delegate;
      
      private final Flux<RouteDefinition> routeDefinitions;
      
      //收集Route
      private final Map<String, List> cache = new HashMap<>();
      
      public CachingRouteDefinitionLocator(RouteDefinitionLocator delegate) {
          this.delegate = delegate;
          routeDefinitions = CacheFlux.lookup(cache, "routeDefs", RouteDefinition.class)
                  .onCacheMissResume(this.delegate::getRouteDefinitions);
      
      }
      
      @Override
      public Flux<RouteDefinition> getRouteDefinitions() {
          return this.routeDefinitions;
      }
    }
    
  • CompositeRouteDefinitionLocator:组合多种 RouteDefinitionLocator 的实现,为 RouteDefinitionRouteLocator 提供统一入口

    public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator {
      //RouteDefinitionLocator 数组
      private final Flux<RouteDefinitionLocator> delegates;
      
      public CompositeRouteDefinitionLocator(Flux<RouteDefinitionLocator> delegates) {
          this.delegates = delegates;
      }
      
      //将组合的 delegates 的路由定义全部返回。
      @Override
      public Flux<RouteDefinition> getRouteDefinitions() {
          return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions);
      }
      
    }
    

到这里外部化的配置的多种方式全部解析完毕。接下来看一下编程方式是怎么样的。

3.2 编程方式

直接拿官方的例子来阐述:

//org.springframework.cloud.gateway.sample.GatewaySampleApplication
public class GatewaySampleApplication {
  //...省略
	@Bean
	public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
		//① RouteLocatorBuilder 可以构建多个路由信息。
		return builder.routes()
				//② 指定了 Predicates,这里包含两个:
				//请求头Host需要匹配**.abc.org,通过 HostRoutePredicateFactory 产生。
				//请求路径需要匹配/anything/png,通过 PathRoutePredicateFactory 产生。
				.route(r -> r.host("**.abc.org").and().path("/anything/png")
						//③ 指定了一个 Filter,下游服务响应后添加响应头X-TestHeader:foobar,
						//通过AddResponseHeaderGatewayFilterFactory 产生。
					.filters(f ->
							f.prefixPath("/httpbin")
									.addResponseHeader("X-TestHeader", "foobar"))
					.uri(uri) //④ 指定路由转发的目的地 uri。
				)
        //...省略
        //⑤ 创建RouteLocator实例
				.build();
	}
  • ① 使用Builder 模式构建Route

  • ② 创建Predicates

  • ③ 创建Filter

  • ④ 需要转发的目的地 uri

  • ⑤ 创建RouteLocator实例

    //返回一个RouteLocator
    public RouteLocator build() {
              return () -> Flux.fromIterable(this.routes)
               //①
                      .map(routeBuilder -> routeBuilder.build());
          }
      
    //RouteLocator 是Route 集合。
    public interface RouteLocator {
      
      Flux<Route> getRoutes();
      
    }
    

    上面build 方法返回的是 RouteLocator对象,它是Route 的集合所以上面 中的build方法就是对应的Route

    public class Route implements Ordered {
      
      //id,标识符,区别于其他 Route。
      private final String id;
      
      //destination uri,路由指向的目的地 uri,即客户端请求最终被转发的目的地。
      private final URI uri;
      
      //order,用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。
      private final int order;
      
      //predicate,谓语,表示匹配该 Route 的前置条件,即满足相应的条件才会被路由到目的地 uri。
      private final AsyncPredicate<ServerWebExchange> predicate;
      
      //gateway filters,过滤器用于处理切面逻辑,如路由转发前修改请求头等。
      private final List<GatewayFilter> gatewayFilters;
        
      //对应①处
      public Route build() {
              Assert.notNull(this.id, "id can not be null");
              Assert.notNull(this.uri, "uri can not be null");
              AsyncPredicate<ServerWebExchange> predicate = getPredicate();
              Assert.notNull(predicate, "predicate can not be null");
      
              return new Route(this.id, this.uri, this.order, predicate,
                      this.gatewayFilters);
          }
    }	
    

    这里的 AsyncPredicate 、 GatewayFilter 在 核心组件部分已经分析过了,就不重复分析了。

到这里,外部化配置的Route、Predicate、Filter会被映射成 RouteDefinition、FilterDefinition、PredicateDefinition,而 编码方式会被映射成 Route、AsyncPredicate 、GatewayFilter。

有时候我们可能又有外部化配置又有编码方式的配置,那么这时候就需要有一个转换。那接下来就看一下Gateway 是如何转换的。


4.RouteDefinitionRouteLocator

将外部化配置的 RouteDefinition、FilterDefinition、PredicateDefinition 转换成Route、AsyncPredicate 、GatewayFilter。

首先还是回到Gateway(一)初始化 文章中的核心配置类GatewayAutoConfiguration ,给我提供了分析的入口,代码如下:

//① GatewayProperties 
//② RouteDefinitionLocator 
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
			List<GatewayFilterFactory> gatewayFilters,
			List<RoutePredicateFactory> predicates,
			RouteDefinitionLocator routeDefinitionLocator,
			@Qualifier("webFluxConversionService") ConversionService conversionService) {
    //③ 这里进行转换
		return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates,
				gatewayFilters, properties, conversionService);
	}
  • ①、② 在上面讲过外部化配置讲过。这里就不重复阐述了

  • 外部化配置进行转换,RouteDefinitionRouteLocator 是 RouteLocator 的实现

    public class RouteDefinitionRouteLocator
          implements RouteLocator, BeanFactoryAware, ApplicationEventPublisherAware {
      
      /**
       * Default filters name.
       */
      public static final String DEFAULT_FILTERS = "defaultFilters";
      
      // RouteDefinition Locator,一个 RouteDefinitionLocator 对象。
      private final RouteDefinitionLocator routeDefinitionLocator;
      
      /**
       * predicates factories,Predicate 工厂列表,会被映射成 key 为 name, value 为 factory 的 Map。
       * 可以猜想出 gateway 是如何根据 PredicateDefinition 中定义的 name 来匹配到相对应的 factory 了。
       * key :{@link RoutePredicateFactory#name()}
       */
      private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>();
      
      /**filter factories,Gateway Filter 工厂列表,同样会被映射成 key 为 name, value 为 factory 的 Map。
       * key :{@link GatewayFilterFactory#name()}
       */
      private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>();
      
      //gateway properties,外部化配置类。
      private final GatewayProperties gatewayProperties;
      
      
      public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,
              List<RoutePredicateFactory> predicates,
              List<GatewayFilterFactory> gatewayFilterFactories,
              GatewayProperties gatewayProperties, ConversionService conversionService) {
          // 设置 RouteDefinitionLocator
          this.routeDefinitionLocator = routeDefinitionLocator;
          this.conversionService = conversionService;
          // ① 初始化 RoutePredicateFactory
          initFactories(predicates);
          // ② 初始化 gatewayFilterFactories
          gatewayFilterFactories.forEach(
                  factory -> this.gatewayFilterFactories.put(factory.name(), factory));
          // 设置 GatewayProperties
          this.gatewayProperties = gatewayProperties;
      }
        
      //③ 实现 RouteLocator 的 getRoutes() 方法 获取route
      @Override
      public Flux<Route> getRoutes() {
          //调用 convertToRoute 方法将 RouteDefinition 转换成 Route。
          return this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute)
                  // TODO: error handling
                  .map(route -> {
                      if (logger.isDebugEnabled()) {
                          logger.debug("RouteDefinition matched: " + route.getId());
                      }
                      return route;
                  });
      }
    }
    
    • ① 初始化 RoutePredicateFactory

      private void initFactories(List<RoutePredicateFactory> predicates) {
              predicates.forEach(factory -> {
                  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.");
                  }
                  this.predicates.put(key, factory);
                  if (logger.isInfoEnabled()) {
                      logger.info("Loaded RoutePredicateFactory [" + key + "]");
                  }
              });
          }
      
    • ② 初始化 gatewayFilterFactories

      //FilterDefinition 转换成 GatewayFilter
      private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) {
              List<GatewayFilter> filters = new ArrayList<>();
          
              // TODO: support option to apply defaults after route specific filters?
              //① 处理 GatewayProperties 中定义的默认的 FilterDefinition,转换成 GatewayFilter。
              if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {
                  filters.addAll(loadGatewayFilters(DEFAULT_FILTERS,
                          this.gatewayProperties.getDefaultFilters()));
              }
          
              //② 将 RouteDefinition 中定义的 FilterDefinition 转换成 GatewayFilter。
              if (!routeDefinition.getFilters().isEmpty()) {
                  filters.addAll(loadGatewayFilters(routeDefinition.getId(),
                          routeDefinition.getFilters()));
              }
          
              //③ 对 GatewayFilter 进行排序,排序的详细逻辑请查阅 spring 中的 Ordered 接口。
              AnnotationAwareOrderComparator.sort(filters);
              return filters;
          }
      
    • ③ 实现 RouteLocator 的 getRoutes() 方法 获取Route,真正转换的方法

      @Override
      public Flux<Route> getRoutes() {
              //调用 convertToRoute 方法将 RouteDefinition 转换成 Route。
              return this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute)
                      // TODO: error handling
                      .map(route -> {
                          if (logger.isDebugEnabled()) {
                              logger.debug("RouteDefinition matched: " + route.getId());
                          }
                          return route;
                      });
          }
          
          private Route convertToRoute(RouteDefinition routeDefinition) {
              //3.1 将 PredicateDefinition 转换成 AsyncPredicate。
              AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
              //3.2 将 FilterDefinition 转换成 GatewayFilter。
              List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);
          
              //3.3 根据 1 和 2 两步骤定义的变量生成 Route 对象。
              return Route.async(routeDefinition).asyncPredicate(predicate)
                      .replaceFilters(gatewayFilters).build();
          }
      
      • 3.1 将 PredicateDefinition 转换成 AsyncPredicate。

        private AsyncPredicate<ServerWebExchange> combinePredicates(
                  RouteDefinition routeDefinition) {
              List<PredicateDefinition> predicates = routeDefinition.getPredicates();
              //① 调用 lookup 方法,将列表中第一个 PredicateDefinition 转换成 AsyncPredicate。
              AsyncPredicate<ServerWebExchange> predicate = lookup(routeDefinition,
                      predicates.get(0));
              
              for (PredicateDefinition andPredicate : predicates.subList(1,
                      predicates.size())) {
                  //② 循环调用,将列表中每一个 PredicateDefinition 都转换成 AsyncPredicate。
                  AsyncPredicate<ServerWebExchange> found = lookup(routeDefinition,
                          andPredicate);
                  //③ 应用and操作,将所有的 AsyncPredicate 组合成一个 AsyncPredicate 对象。
                  predicate = predicate.and(found);
              }
              
              return predicate;
          }
        
      • 3.2 将 FilterDefinition 转换成 GatewayFilter。

        private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) {
              List<GatewayFilter> filters = new ArrayList<>();
              
              // TODO: support option to apply defaults after route specific filters?
              //① 处理 GatewayProperties 中定义的默认的 FilterDefinition,转换成 GatewayFilter。
              if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {
                  filters.addAll(loadGatewayFilters(DEFAULT_FILTERS,
                          this.gatewayProperties.getDefaultFilters()));
              }
              
              //② 将 RouteDefinition 中定义的 FilterDefinition 转换成 GatewayFilter。
              if (!routeDefinition.getFilters().isEmpty()) {
                  filters.addAll(loadGatewayFilters(routeDefinition.getId(),
                          routeDefinition.getFilters()));
              }
              
              //③ 对 GatewayFilter 进行排序,排序的详细逻辑请查阅 spring 中的 Ordered 接口。
              AnnotationAwareOrderComparator.sort(filters);
              return filters;
          }
        
      • 3.3 根据 1 和 2 两步骤定义的变量生成 Route 对象。

        public Route build() {
                  Assert.notNull(this.id, "id can not be null");
                  Assert.notNull(this.uri, "uri can not be null");
                  AsyncPredicate<ServerWebExchange> predicate = getPredicate();
                  Assert.notNull(predicate, "predicate can not be null");
              
                  return new Route(this.id, this.uri, this.order, predicate,
                          this.gatewayFilters);
              }
        

    到这里我们就已经知道外置配合是如何转换的,那么现在Route 已经组装完毕了,现在就是看一下Route里面的Predicate 和 Filter的实现,都比较简单


总结:

  • 通过 RouteDefinitionLocator 的各种实现,来多样化的获取不同的外置配置。(properties、数据库、注册中心等)
  • 根据 RouteDefinitionRouteLocator 把这些外置配置转存成 Route。