引言
上篇实现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实例,然后进行调用。
@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的类图
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对象,然后组装过滤器,GlobalFilter和GatewayFilter合并成一个列表,最后放到过滤链中进行逻辑处理。
总结
通过以上一步一步的梳理,终于搞明白了Spring Cloud Gateway 是如何和WebFlux进行结合的,并且其中最重要的Filter是如何工作的。
以上,如果有哪里不对,欢迎讨论。
References
[1] 实现Spring Cloud Gateway 动态路由和内置过滤器: https://juejin.cn/post/7038231474465669157