Spring Cloud Gateway和Spring WebFlux的契合点

3,541 阅读3分钟

引言

上篇实现Spring Cloud Gateway 动态路由和内置过滤器 介绍了基于Spring Cloud Gateway实现动态路由和内置的过滤器。本着刨根问底的精神,我不禁想到Spring Cloud Gateway的过滤器是如何加载的? 我们都知道在Spring 5.X版本中出现了WebFlux,它是Spring 5.x框架的一部分,为Web应用提供了响应式编程的支持;而Spring Cloud Gateway 又是基于WebFlux实现的。

所以,今天我们来看看Spring Cloud Gateway是如何与Web Flxu进行契合的。

Gateway 切入点

我们知道在SpringMVC当中是通过DispatcherServlet进行请求的处理和转发的,而在Spring WebFlux当中也有类似的类:DispatcherHandler

DispatcherHandler类中,我们可以在handle方法当中看到根据当前的请求信息ServerWebExchangehandlerMappings属性中获取对应的Handler实例,然后进行调用。

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
   if (this.handlerMappings == null) {
      return createNotFoundError();
   }
   if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
      return handlePreFlight(exchange);
   }
   return Flux.fromIterable(this.handlerMappings)
          // 获取handler实例
         .concatMap(mapping -> mapping.getHandler(exchange))
         .next()
         .switchIfEmpty(createNotFoundError())
         // 调用handler方法
         .flatMap(handler -> invokeHandler(exchange, handler))
         // 处理结果
         .flatMap(result -> handleResult(exchange, result));
}

我们再看看handlerMappings属性,它是一个List<HandlerMapping>列表,该属性的值是通过initStrategies方法从Spring容器当中获取的。

protected void initStrategies(ApplicationContext context) {
   Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
         context, HandlerMapping.class, true, false);

   ArrayList<HandlerMapping> mappings = new ArrayList<>(mappingBeans.values());
   AnnotationAwareOrderComparator.sort(mappings);
   this.handlerMappings = Collections.unmodifiableList(mappings);
   ...
}

而在众多的HandlerMapping实例当中,我们可以找到其中一个Spring Cloud Gateway的实现类:RoutePredicateHandlerMapping, 这个类是在GatewayAutoConfiguration类中通过@Bean注解进行实例化的,其中还通过构造器注入了一个很重要的实例:RouteLocator

到这里,我们回头看下在DispatcherHandler类的handle方法中,是通过调用HandlerMapping#getHandler方法返回某个handler对象,然后在进行调用。

所以,在RoutePredicateHandlerMapping实例当中,其实就是调用了父类的getHandler方法,继而调用了RoutePredicateHandlerMapping#getHandlerInternal方法。

protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
   if (this.managementPortType == DIFFERENT && this.managementPort != null
         && exchange.getRequest().getURI().getPort() == this.managementPort) {
      return Mono.empty();
   }
   exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());

   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);
            }
            // 把匹配的路由实例缓存到当前请求的上下文中
            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) + "]");
            }
         })));
}

最后会走到RoutePredicateHandlerMapping#lookupRoute方法。

protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
   // 遍历Route列表
   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;
         });
}

看到这里,想必你已经明白了,最后是根据当前请求对路由进行匹配,如果匹配则缓存到当前请求信息上下文中,然后返回对应的FilteringWebHandler实例。

最后,放一张RoutePredicateHandlerMapping的类图

image.png

Gateway Filter 处理流程

通过上一节,我们已经明白了Gateway是如何切入WebFlux的处理流程的。现在我们再看看对于Spring Cloud Gateway 的过滤器框架是如何处理的。

在说Spring Cloud Gateway的过滤器处理流程之前,我们需要先知道在Gateway有2种类型的过滤器:

  • GlobalFilter 公共类型的过滤器,所有的路由都会加载这种类型的过滤器
  • GatewayFilter 特殊类型的过滤器,只有显示的挂载到某个过滤器,才会进行对应的逻辑处理

在上一节,我们知道DispatcherHandler#handle方法最后是做了2个事情:

  1. 根据请求信息匹配到对应的路由对象,并且放到当前请求的上下文中
  2. 返回FilteringWebHandler实例

我们接着往下走,在DispatcherHandler#invokeHandler方法进行方法调用,会走到FilteringWebHandler#handle

public Mono<Void> handle(ServerWebExchange exchange) {
   Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
   List<GatewayFilter> gatewayFilters = route.getFilters();

   List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
   combined.addAll(gatewayFilters);
   AnnotationAwareOrderComparator.sort(combined);

   if (logger.isDebugEnabled()) {
      logger.debug("Sorted gatewayFilterFactories: " + combined);
   }

   return new DefaultGatewayFilterChain(combined).filter(exchange);
}

在该方法内,从当前请求的上下文取回Route对象,然后组装过滤器,GlobalFilterGatewayFilter合并成一个列表,最后放到过滤链中进行逻辑处理。

总结

通过以上一步一步的梳理,终于搞明白了Spring Cloud Gateway 是如何和WebFlux进行结合的,并且其中最重要的Filter是如何工作的。

以上,如果有哪里不对,欢迎讨论。

有问题的话,欢迎添加个人微信好友进行讨论,个人微信: daydaycoupons