Spring Cloud Gateway和Spring WebFlux的契合點

引言

上篇

實現Spring Cloud Gateway 動態路由和內建過濾器[1]

介紹了基於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

方法當中看到根據當前的請求資訊

ServerWebExchange

handlerMappings

屬性中獲取對應的Handler例項,然後進行呼叫。

@Overridepublic Mono 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

列表,該屬性的值是透過initStrategies方法從Spring容器當中獲取的。

protected void initStrategies(ApplicationContext context) { Map mappingBeans = BeanFactoryUtils。beansOfTypeIncludingAncestors( context, HandlerMapping。class, true, false); ArrayList 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>) 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 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

的類圖

Spring Cloud Gateway和Spring WebFlux的契合點

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 handle(ServerWebExchange exchange) { Route route = exchange。getRequiredAttribute(GATEWAY_ROUTE_ATTR); List gatewayFilters = route。getFilters(); List 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物件,然後組裝過濾器,

GlobalFilter

GatewayFilter

合併成一個列表,最後放到過濾鏈中進行邏輯處理。

總結

透過以上一步一步的梳理,終於搞明白了Spring Cloud Gateway 是如何和WebFlux進行結合的,並且其中最重要的Filter是如何工作的。

以上,如果有哪裡不對,歡迎討論。

References

[1] 實現Spring Cloud Gateway 動態路由和內建過濾器:

https://juejin。cn/post/7038231474465669157