Route 加载流程


Route
加载

网关服务核心功能是路由转发,即将接收的请求如何正确的路由到下层具体的服务模块。下面分析下这些路由信息构建的流程。

路由信息配置文件:

spring:
  cloud:
    gateway:
      routes:
        - id: cloud-oauth2
          uri: lb://cloud-oauth2
          order: 8001
          predicates:
            - Path=/cloud-oauth2/**
          filters:
            - StripPrefix=1
        - id: cloud-biz
          uri: lb://cloud-biz
          order: 8003
          predicates:
            - Path=/cloud-biz/**
          filters:
            - StripPrefix=1

上面就是包含有两条路由信息的配置文件,
Gateway
将其加载解析最终在内存中的数据结构 Route

public class Route implements Ordered {

    /**
     * 路由编号
     * ID 编号,唯一
     */
    private final String id;

    /**
     * 路由目的 URI
     *
     */
    private final URI uri;

    /**
     * 顺序
     * 当请求匹配到多个路由时,使用顺序小的
     */
    private final int order;

    /**
     * 谓语数组
     * 请求通过 predicates 判断是否匹配
     */
    private final Predicate predicate;

    /**
     * 过滤器数组
     */
    private final List gatewayFilters;
}
由代码可以看到一个路由应该包含如下必要的信息:

  • id:路由编号,唯一
  • uri:路由向的 URI,对应的具体业务服务的URL
  • order:顺序,当请求匹配多个路由时,使用顺序小的
  • predicate: 请求匹配路由的断言条件
  • gatewayFilters: 当前路由上存在的过滤器,用于对请求做拦截处理

流程分析

1、路由配置加载

通过 @ConfigurationProperties("spring.cloud.gateway")
配注解将配置文件中路由规则信息加载到 GatewayProperties
对象中,其中路由信息会被解析成 RouteDefinition
结构。

@ConfigurationProperties("spring.cloud.gateway")
@Validated
public class GatewayProperties {

    /**
     * List of Routes.
     */
    @NotNull
    @Valid
    private List routes = new ArrayList();

    /**
     * List of filter definitions that are applied to every route.
     */
    private List defaultFilters = new ArrayList();

    private List streamingMediaTypes = Arrays
            .asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON);
}

路由定义 RouteDefinition
代码见下:

/**
 * 路由定义实体信息,包含路由的定义信息
 * @author Spencer Gibb
 */
@Validated
public class RouteDefinition {

    /**
     * 路由ID 编号,唯一
     */
    @NotEmpty
    private String id = UUID.randomUUID().toString();

    /**
     * 谓语定义数组
     * predicates 属性,谓语定义数组
     * 请求通过 predicates 判断是否匹配。在 Route 里,PredicateDefinition 转换成 Predicate
     */
    @NotEmpty
    @Valid
    private List predicates = new ArrayList();

    /**
     *过滤器定义数组
     * filters 属性,过滤器定义数组。
     * 在 Route 里,FilterDefinition 转换成 GatewayFilter
     */
    @Valid
    private List filters = new ArrayList();

    /**
     * 路由指向的URI
     */
    @NotNull
    private URI uri;

    /**
     * 顺序
     */
    private int order = 0;
}

结构比较简单,和文件中的配置是一一对应的,其中包含了两个集合分别用于存储
路由断言器的 Definition

路由过滤器的 Definition

;其中, PredicateDefinition
会转换成 Predicate
,而 FilterDefinition
会被转换成 GatewayFilter

2、获取Route集合

路由配置文件已被加载到 GateProperties
中,其中具体路由也被存储到 RouteDefinition
中,下面看下如何进行转换。

首先, RouteRefreshListener
类监听 ContextRefreshedEvent
事件,后触发 RefreshRoutesEvent
事件:

public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ContextRefreshedEvent
            || event instanceof RefreshScopeRefreshedEvent
            || event instanceof InstanceRegisteredEvent) {
        reset();
    }
    else if (event instanceof ParentHeartbeatEvent) {
        ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
        resetIfNeeded(e.getValue());
    }
    else if (event instanceof HeartbeatEvent) {
        HeartbeatEvent e = (HeartbeatEvent) event;
        resetIfNeeded(e.getValue());
    }
}

private void reset() {
    this.publisher.publishEvent(new RefreshRoutesEvent(this));
}

Tips
ContextRefreshedEvent
是在 ApplicationContext.refresh()
执行完成后触发,即 Context
初始化全部完成。

WeightCalculatorWebFilter
类监听到 RefreshRoutesEvent
事件,触发调用 CachingRouteLocator#getRoutes()
获取 Route
集合:

public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof PredicateArgsEvent) {
        handle((PredicateArgsEvent) event);
    }
    else if (event instanceof WeightDefinedEvent) {
        addWeightConfig(((WeightDefinedEvent) event).getWeightConfig());
    }
        //routeLocator内部包装的就是CachingRouteLocator实例
    else if (event instanceof RefreshRoutesEvent && routeLocator != null) {//监听RefreshRoutesEvent
        routeLocator.ifAvailable(locator -> locator.getRoutes().subscribe()); //CachingRouteLocator中routes被真正初始化
    }
}

CachingRouteLocator#getRoutes()
方法只是简单返回内部变量 routes

public Flux getRoutes() {
    return this.routes;
}

3、 routes
初始化流程

routes
在构造方法中进行初始化:

public CachingRouteLocator(RouteLocator delegate) {
    this.delegate = delegate;
    routes = CacheFlux.lookup(cache, "routes", Route.class)
        .onCacheMissResume(() -> this.delegate.getRoutes()//
            .sort(AnnotationAwareOrderComparator.INSTANCE));//sort
}

Tips
:构造方法中初始化 routes
,由于是异步的这时并没有真正的触发底层执行,只有在调用 locator.getRoutes()
真正使用到 routes
时才会触发底层调用。所以, WeightCalculatorWebFilter
中监听事件调用 locator.getRoutes()
就是触发执行。

从代码看, delegate
代理具体类型是 CachingRouteLocator
-> CompositeRouteLocator
CompositeRouteLocator
,汇聚了所有的 RouteLocator
集合,主要包含两类:一类是 RouteDefinitionRouteLocator
,基于 RouteDefinition
获取 Route
;另一类是编程方式自定义创建 RouteLocator
,如:

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { 
    return builder.routes() 
            .route(r -> r.host("**.abc.org").and().path("/image/png") 
                .filters(f ->
                        f.addResponseHeader("X-TestHeader", "foobar")) 
                .uri("http://httpbin.org:80") 
            )
            .build();
}

这里我们主要分析 RouteDefinition
Route
流程,所以需要关注 RouteDefinitionRouteLocator#getRoutes

public Flux getRoutes() {
    //routeDefinitionLocator即是CompositeRouteDefinitionLocator实例
    return this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute)
        .map(route -> {
            if (logger.isDebugEnabled()) {
                logger.debug("RouteDefinition matched: " + route.getId());
            }
            return route;
        });
}

routeDefinitionLocator
是一个 CompositeRouteDefinitionLocator
类型实例,将 InMemoryRouteDefinitionRepository
PropertiesRouteDefinitionLocator
包装混合到一起。

public Flux getRouteDefinitions() {
    return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions);
}

Tips
:这里涉及的各种代理关系后续分析自动装配类 GatewayAutoConfiguration
再来细说。

routeDefinitionLocator.getRouteDefinitions()
实际上调用 InMemoryRouteDefinitionRepository
PropertiesRouteDefinitionLocator
getRouteDefinitions
方法获取到 RouteDefinition
集合,然后执行 convertToRoute()
方法将 RouteDefinition
转成 Route
对象。

private Route convertToRoute(RouteDefinition routeDefinition) {
    //解析Predicate,将PredicateDefinition转出Predicate
    AsyncPredicate predicate = combinePredicates(routeDefinition);
    //解析GatewayFilter,将FilterDefinition转成GatewayFilter,包括配置中定义的默认Filter和直接配置的Filter,不包括全局过滤器
    List gatewayFilters = getFilters(routeDefinition);

    return Route.async(routeDefinition).asyncPredicate(predicate)
            .replaceFilters(gatewayFilters).build();
}

这里主要关注下 getFilters(routeDefinition)
如何将 FilterDefinition
转成 GatewayFilter

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters

private List getFilters(RouteDefinition routeDefinition) {
    List filters = new ArrayList();

    // 加载default filter
    if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {
        filters.addAll(loadGatewayFilters(DEFAULT_FILTERS,
                this.gatewayProperties.getDefaultFilters()));
    }

        // 加载指定filter
    if (!routeDefinition.getFilters().isEmpty()) {
        filters.addAll(loadGatewayFilters(routeDefinition.getId(),
                routeDefinition.getFilters()));
    }

        //对Filter进行排序
    AnnotationAwareOrderComparator.sort(filters);
    return filters;
}

接下来才是真正进行转换的逻辑, org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters

List loadGatewayFilters(String id,
        List filterDefinitions) {
    //遍历Route的filterDefinitions,将过滤器定义转换成对应的过滤器
    ArrayList ordered = new ArrayList(filterDefinitions.size());
    for (int i = 0; i < filterDefinitions.size(); i++) {
        FilterDefinition definition = filterDefinitions.get(i);
                //获取匹配的GatewayFilterFactory
        GatewayFilterFactory factory = this.gatewayFilterFactories
                .get(definition.getName());
        if (factory == null) {
            throw new IllegalArgumentException(
                    "Unable to find GatewayFilterFactory with name "
                            + definition.getName());
        }
                //获取参数,格式如:_genkey_0:1,_genkey_1:2格式,对配置中参数使用逗号分隔
        Map args = definition.getArgs();
        if (logger.isDebugEnabled()) {
            logger.debug("RouteDefinition " + id + " applying filter " + args + " to "
                    + definition.getName());
        }
        //参数解析,解析出参数名称和解析支持SpEL表达式值#{}
        Map properties = factory.shortcutType().normalize(args,
                factory, this.parser, this.beanFactory);
        //创建Configuration实例,这个是GatewayFilterFactory实现类中指定的,如:
                /*
            public StripPrefixGatewayFilterFactory() {
            super(Config.class);//这里指定Configuration的Class
        }
                */
        Object configuration = factory.newConfig();
        //通过反射将解析的参数设置到创建的Configuration实例中
        ConfigurationUtils.bind(configuration, properties,
                factory.shortcutFieldPrefix(), definition.getName(), validator);

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

                //通过过滤器工厂创建GatewayFilter
        GatewayFilter gatewayFilter = factory.apply(configuration);
        if (this.publisher != null) {
                    //发布事件
            this.publisher.publishEvent(new FilterArgsEvent(this, id, properties));
        }
        if (gatewayFilter instanceof Ordered) {//Ordered类型的Filter直接添加
            ordered.add(gatewayFilter);
        }
        else {//非Order类型的Filter,转成Order类型的,每个Route下从1开始顺序递增
            ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));
        }
    }
    return ordered;
}

4、参数解析

org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType#normalize

DEFAULT {
    @Override
    public Map normalize(Map args,
            ShortcutConfigurable shortcutConf, SpelExpressionParser parser,
            BeanFactory beanFactory) {
        Map map = new HashMap();
        int entryIdx = 0;
        for (Map.Entry entry : args.entrySet()) {
                        //格式化key,解析出的“_genkey_0”、“_genkey_1”,根据GatewayFilter#shortcutFieldOrder配置的参数名称集合匹配
            String key = normalizeKey(entry.getKey(), entryIdx, shortcutConf, args);
                        //解析value,支持Spring SpEL表达式:#{}
            Object value = getValue(parser, beanFactory, entry.getValue());

            map.put(key, value);
            entryIdx++;
        }
        return map;
    }
}

normalizeKey()
定义如下:

static String normalizeKey(String key, int entryIdx, ShortcutConfigurable argHints,
            Map args) {
    // RoutePredicateFactory has name hints and this has a fake key name
    // replace with the matching key hint
    if (key.startsWith(NameUtils.GENERATED_NAME_PREFIX)
            && !argHints.shortcutFieldOrder().isEmpty() && entryIdx < args.size()
            && entryIdx < argHints.shortcutFieldOrder().size()) {
                //调用ShortcutConfigurable#shortcutFieldOrder,获取参数名称集合,这个一般是在各自定义GatewayFilterFactory中实现
        key = argHints.shortcutFieldOrder().get(entryIdx);
    }
    return key;
}

StripPrefixGatewayFilterFactory#shortcutFieldOrder
定义如下:

public static final String PARTS_KEY = "parts";

@Override
public List shortcutFieldOrder() {
    return Arrays.asList(PARTS_KEY);
}

5、自定义 GateFilterFactory
总结

分析 GatewayFilter
的加载过程,我们以 StripPrefixGatewayFilterFactory
为例,介绍下在自定义GatewayFilterFactory时,主要注意以下几点:

  • 构造方法中指定配置类类型的 Class

    public StripPrefixGatewayFilterFactory() {
        super(Config.class);
    }
    

  • shortcutFieldOrder()
    方法指定参数名称,按照先后顺序和配置文件中参数进行匹配,同时名称和 Configuration
    中属性名称匹配,这样配置参数就初始化到 Configuration
    实例中

    public static final String PARTS_KEY = "parts";
    
    @Override
    public List shortcutFieldOrder() {
        return Arrays.asList(PARTS_KEY);
    }
    
    public static class Config {
        private int parts;
    }
    

  • GatewayFilterFactory
    类的核心方法 apply(Config config)
    ,输入初始化完成的 Configuration
    实例,一般通过匿名内部类方式构建一个 GatewayFilter
    进行返回,这个 GatewayFilter
    封装的就是我们需要实现的业务逻辑:

    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono filter(ServerWebExchange exchange,
                    GatewayFilterChain chain) {
                ServerHttpRequest request = exchange.getRequest();
                addOriginalRequestUrl(exchange, request.getURI());
                String path = request.getURI().getRawPath();
                String newPath = "/"
                        + Arrays.stream(StringUtils.tokenizeToStringArray(path, "/"))
                                .skip(config.parts).collect(Collectors.joining("/"));
                newPath += (newPath.length() > 1 && path.endsWith("/") ? "/" : "");
                ServerHttpRequest newRequest = request.mutate().path(newPath).build();
    
                exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR,
                        newRequest.getURI());
    
                return chain.filter(exchange.mutate().request(newRequest).build());
            }
    
            @Override
            public String toString() {
                return filterToStringCreator(StripPrefixGatewayFilterFactory.this)
                        .append("parts", config.getParts()).toString();
            }
        };
    }
    

总结

至此, Route
加载以及解析的整个流程分析完成,解析后的 Route
集合数据会被缓存到 CachingRouteLocator.routes
属性中,通过 getRoutes()
可以获取到该数据。