SpringCloud Gateway——谓词原理

SpringCloud Gateway——谓词GatewayPredicate

1. 简介

路由配置:

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: app
          uri: lb://nacos-app
          predicates:
            - Path=/app/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 10
                key-resolver: "#{@routeIdKeyResolver}"

其中predicatesGateway中的谓词,用来筛选属于哪个路由,可以配置多个,只有通过了配置的所有谓词才能使用对应的路由配置。

而谓词的实现跟上篇文章路由过滤器GatewayFilter很相似,接下来通过源码进行分析。

2. 源码分析

1. RoutePredicateFactory

RoutePredicateFactory是生成GatewayPredicate的工厂类接口,这里以最常用的PathRoutePredicateFactory为例。

PathRoutePredicateFactory:路径匹配谓词

@Override
public Predicate<ServerWebExchange> apply(Config config) {
    final ArrayList<PathPattern> pathPatterns = new ArrayList<>();
    // 根据配置好的匹配路径配置路径解析器
    synchronized (this.pathPatternParser) {
        pathPatternParser.setMatchOptionalTrailingSeparator(
            config.isMatchOptionalTrailingSeparator());
        config.getPatterns().forEach(pattern -> {
            PathPattern pathPattern = this.pathPatternParser.parse(pattern);
            pathPatterns.add(pathPattern);
        });
    }
    // 定义GatewayPredicate实现
    return new GatewayPredicate() {
        @Override
        public boolean test(ServerWebExchange exchange) {
            // 解析路径
            PathContainer path = parsePath(
                exchange.getRequest().getURI().getRawPath());

            // 匹配则返回true,否则返回false
            Optional<PathPattern> optionalPathPattern = pathPatterns.stream()
                .filter(pattern -> pattern.matches(path)).findFirst();

            if (optionalPathPattern.isPresent()) {
                PathPattern pathPattern = optionalPathPattern.get();
                traceMatch("Pattern", pathPattern.getPatternString(), path, true);
                PathMatchInfo pathMatchInfo = pathPattern.matchAndExtract(path);
                putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());
                return true;
            }
            else {
                traceMatch("Pattern", config.getPatterns(), path, false);
                return false;
            }
        }

        @Override
        public String toString() {
            return String.format("Paths: %s, match trailing slash: %b",
                                 config.getPatterns(), config.isMatchOptionalTrailingSeparator());
        }
    };
}

每一个RoutePredicateFactory实现主要逻辑在apply()中,在该方法中定义GatewayPredicate接口逻辑。

GatewayAutoConfiguration中实例化了RoutePredicateFactory的每一种实现

GatewayAutoConfiguration

......

@Bean
public PathRoutePredicateFactory pathRoutePredicateFactory() {
    return new PathRoutePredicateFactory();
}

@Bean
public QueryRoutePredicateFactory queryRoutePredicateFactory() {
    return new QueryRoutePredicateFactory();
}

@Bean
public ReadBodyPredicateFactory readBodyPredicateFactory(
    ServerCodecConfigurer codecConfigurer)
 
{
    return new ReadBodyPredicateFactory(codecConfigurer.getReaders());
}

@Bean
public RemoteAddrRoutePredicateFactory remoteAddrRoutePredicateFactory() {
    return new RemoteAddrRoutePredicateFactory();
}

......

2. RouteDefinitionRouteLocator

RouteDefinitionRouteLocator用于将路由配置解析成Route对象,其中就包括谓词解析。

GatewayAutoConfiguration:实例化RouteDefinitionRouteLocator,传入RoutePredicateFactory实例集合

@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
                                                List<GatewayFilterFactory> gatewayFilters,
                                                List<RoutePredicateFactory> predicates,
                                                RouteDefinitionLocator routeDefinitionLocator,
                                                ConfigurationService configurationService)
 
{
    return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates,
                                           gatewayFilters, properties, configurationService);
}

RouteDefinitionRouteLocator

  1. 初始化谓词集合predicates
private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>();

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

private void initFactories(List<RoutePredicateFactory> predicates) {
    // 以factory.name()为key,factory为value存入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 + "]");
        }
    });
}

初始化RouteDefinitionRouteLocator时,将谓词按name()为key,实例为value存入predicates变量中,来看看name()逻辑

RoutePredicateFactory

default String name() {
    return NameUtils.normalizeRoutePredicateName(getClass());
}

NameUtils

public static String normalizeRoutePredicateName(
    Class<? extends RoutePredicateFactory> clazz)
 
{
    // 去掉类名中的RoutePredicateFactory
    return removeGarbage(clazz.getSimpleName()
                         .replace(RoutePredicateFactory.class.getSimpleName(), ""));
}

private static String removeGarbage(String s) {
    int garbageIdx = s.indexOf("$Mockito");
    if (garbageIdx > 0) {
        return s.substring(0, garbageIdx);
    }

    return s;
}

可以看出是去掉类名中的RoutePredicateFactory做为key,这里就对应上了路由配置,比如PathRoutePredicateFactory

predicates:
  - Path=/app/**
  1. 解析路由配置中的谓词

RouteDefinitionRouteLocator

@Override
public Flux<Route> getRoutes() {
    Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions()
        .map(this::convertToRoute);
    ......
}

private Route convertToRoute(RouteDefinition routeDefinition) {
    // 谓词
    AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
    ......
}

private AsyncPredicate<ServerWebExchange> combinePredicates(
    RouteDefinition routeDefinition)
 
{
    List<PredicateDefinition> predicates = routeDefinition.getPredicates();
    if (predicates == null || predicates.isEmpty()) {
        return AsyncPredicate.from(exchange -> true);
    }
    // 查找第一个谓词实现
    AsyncPredicate<ServerWebExchange> predicate = lookup(routeDefinition,
                                                         predicates.get(0));

    for (PredicateDefinition andPredicate : predicates.subList(1,
                                                               predicates.size())) {
        AsyncPredicate<ServerWebExchange> found = lookup(routeDefinition,
                                                         andPredicate);
        // AsyncPredicate是一个链表结构,and()生成AndAsyncPredicate对象表示需要符合所有谓词才匹配
        predicate = predicate.and(found);
    }

    return predicate;
}

private AsyncPredicate<ServerWebExchange> lookup(RouteDefinition route,
                                                 PredicateDefinition predicate)
 
{
    // 根据名称从predicates变量中查找谓词实现
    RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());
    if (factory == null) {
        throw new IllegalArgumentException(
            "Unable to find RoutePredicateFactory with name "
            + predicate.getName());
    }
    if (logger.isDebugEnabled()) {
        logger.debug("RouteDefinition " + route.getId() + " applying "
                     + predicate.getArgs() + " to " + predicate.getName());
    }

    // @formatter:off
    Object config = this.configurationService.with(factory)
        .name(predicate.getName())
        .properties(predicate.getArgs())
        .eventFunction((bound, properties) -> new PredicateArgsEvent(
            RouteDefinitionRouteLocator.this, route.getId(), properties))
        .bind();
    // @formatter:on

    return factory.applyAsync(config);
}

这里通过名称从predicates变量中获取谓词实现,然后调用RoutePredicateFactory.applyAsync()生成AsyncPredicate实例,AsyncPredicate是一个记录左右节点的链表结构,所以最后将多个谓词通过and()合并为一个AsyncPredicate实例。

RoutePredicateFactory.applyAsync():调用RoutePredicateFactory.apply()的具体实现,然后封装成AsyncPredicate,所以谓词的实现类需要重写apply()

default AsyncPredicate<ServerWebExchange> applyAsync(C config) {
    return toAsyncPredicate(apply(config));
}

Predicate<ServerWebExchange> apply(C config);

ServerWebExchangeUtils

public static AsyncPredicate<ServerWebExchange> toAsyncPredicate(
    Predicate<? super ServerWebExchange> predicate)
 
{
    Assert.notNull(predicate, "predicate must not be null");
    return AsyncPredicate.from(predicate);
}

AsyncPredicate:用于保存谓词集合的数据结构

class DefaultAsyncPredicate<Timplements AsyncPredicate<T{

    private final Predicate<T> delegate;

    public DefaultAsyncPredicate(Predicate<T> delegate) {
        this.delegate = delegate;
    }

    @Override
    public Publisher<Boolean> apply(T t) {
        // 执行GatewayPredicate.test()
        return Mono.just(delegate.test(t));
    }

    @Override
    public String toString() {
        return this.delegate.toString();
    }

}

default AsyncPredicate<T> and(AsyncPredicate<? super T> other) {
    return new AndAsyncPredicate<>(this, other);
}

class AndAsyncPredicate<Timplements AsyncPredicate<T{

    private final AsyncPredicate<? super T> left;

    private final AsyncPredicate<? super T> right;

    public AndAsyncPredicate(AsyncPredicate<? super T> left,
                             AsyncPredicate<? super T> right)
 
{
        Assert.notNull(left, "Left AsyncPredicate must not be null");
        Assert.notNull(right, "Right AsyncPredicate must not be null");
        this.left = left;
        this.right = right;
    }

    @Override
    public Publisher<Boolean> apply(T t) {
        return Mono.from(left.apply(t)).flatMap(
            result -> !result ? Mono.just(false) : Mono.from(right.apply(t)));
    }
}

调用AsyncPredicate.and()会生成AndAsyncPredicate对象,AndAsyncPredicate是一个链表结构,保存着上一个(left)AsyncPredicate和下一个(right)AsyncPredicate,当执行apply()时,会从上往下执行对应实例的apply(),只有通过所有的谓词才返回true。而每一个实例都是一个DefaultAsyncPredicate,其中的apply()调用了GatewayPredicate.test(),所以在定义GatewayPredicate的逻辑时为什么要重写test()

3. RoutePredicateHandlerMapping

那什么时候执行谓词的apply()呢?在请求转发源码分析一文中有个流程图

SpringCloud Gateway——谓词原理

请求转发的流程是,根据谓词筛选出对应的路由,然后执行相应的过滤器。从类名中可以看出RoutePredicateHandlerMapping是根据谓词筛选路由的逻辑。

RoutePredicateHandlerMapping

@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
    ......

    // 查找对应的Route
    return lookupRoute(exchange)
        .flatMap((Function<Route, Mono<?>>) r -> {
            exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
            if (logger.isDebugEnabled()) {
                logger.debug(
                    "Mapping [" + getExchangeDesc(exchange) + "] to " + r);
            }

            // 将对应的Route做为一个属性放到exchange
            exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
            // 返回FilteringWebHandler
            return Mono.just(webHandler);
        }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
        exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
        if (logger.isTraceEnabled()) {
            logger.trace("No RouteDefinition found for ["
                         + getExchangeDesc(exchange) + "]");
        }
    })));
}

/**
 * 查找符合条件的Route
 * @param exchange
 * @return
 */

protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
    return this.routeLocator.getRoutes()
        .concatMap(route -> Mono.just(route).filterWhen(r -> {
            exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
            // 执行谓词,是否符合
            return r.getPredicate().apply(exchange);
        })
                   .doOnError(e -> logger.error(
                       "Error applying predicate for route: " + route.getId(),
                       e))
                   .onErrorResume(e -> Mono.empty()))
        .next()
        .map(route -> {
            if (logger.isDebugEnabled()) {
                logger.debug("Route matched: " + route.getId());
            }
            validateRoute(route, exchange);
            return route;
        });
}

世界那么大,感谢遇见,未来可期…

欢迎同频共振的那一部分人

作者公众号:Tarzan写bug


原文始发于微信公众号(Tarzan写bug):SpringCloud Gateway——谓词原理

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/45884.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!