当前位置: 首页 > news >正文

再谈SpringCloud Gateway源码

再谈SpringCloud Gateway源码

    • 一、整体请求流程
    • 二、前置对象准备
      • 1、实例化HandlerMapping
      • 2、实例化Route
      • 3、实例化WebHandler
    • 三、实践业务扩展点
      • 1、定义扩展Route对象
      • 2、Filter能做什么
      • 3、定义扩展Filter对象
      • 4、定义父类Filter简化请求参数处理

前言:
之前有阅读过一次SpringCloud Gateway的源码,不过那时更多的是浮于表面,走了一遍流程。直到现在工作中真的遇到了基于SpringCloud Gateway的业务开发,才发现源码中很多机制还是不熟悉,于时又重新学习了一遍源码,并做此记录

前置知识:

  1. Reactive Streams&Reactor Core
  2. Spring WebFlux
  3. 浅谈Spring Cloud Gateway源码


一、整体请求流程

SpringCloud Gateway是基于Spring WebFlux完成请求的分发,整合SpringCloud Gateway后请求分发流程如下图:




二、前置对象准备

上面的流程更多的是描述当一个请求进来以后的分发流程,但是涉及到的一些对象都是在项目启动阶段完成的实例化

1、实例化HandlerMapping

在SpringCloud Gateway中会进行自动装配,自动创建一个RoutePredicateHandlerMapping,用于后续处理请求。当然我们也可以自定义,只要顺序在他的前面就可以(这也是常见自定义HandlerMapping的操作)


2、实例化Route

该部分逻辑涉及实例化对象较多

在HandlerMapping中,会涉及当前请求url与Route中定义的请求url匹配的逻辑,匹配命中则代表是网关请求,如果无法匹配命中则继续使用后续的handlerMapping判定,如当前项目上的创建的controller的接口的HandlerMapping。

1)RouteDefinitionLocator:实例化CompositeRouteDefinitionLocator

CompositeRouteDefinitionLocator可以看做是RouteDefinitionLocator的包装器,用于遍历访问所有RouteDefinitionLocator中定义的RouteDefinition。

在使用侧,我们只需要按照RouteDefinitionLocator中RouteDefinition的标准定义对象,即可完成自定义的添加


2)RouteLocator:实例化RouteDefinitionRouteLocator

这里会借助GatewayProperties、GatewayFilterFactory、RoutePredicateFactory,以及上一步创建的CompositeRouteDefinitionLocator(内部包含所有的RouteDefinition)来实例化该对象。

请注意,由于@Primary注解的存在,Gateway中使用的RouteLocator其实是下面的CachingRouteLocator,不过在CachingRouteLocator中,只是对所有的Route进行了缓存

值得一提的是,这里的CompositeRouteLocator和前面的CompositeRouteDefinitionLocator的作用都是一致的,都是完成对所RouteLocator的包装,尽管示例代码中只有一个,但在真实的业务场景中,我们同样可以进行自定义扩展

注意:记住前面的RouteDefinitionLocator和RouteLocator,都是被Composite包装过一次,并且逻辑都是封装遍历。明白这个对于后续业务代码的执行流程至关重要。


3)Route:实例化Route

回顾最整体的请求流程,在获取HandlerMapping的时候,会调用到getHandlerInternal方法(org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#getHandlerInternal。再往后走会调用到lookupRoute方法(org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#lookupRoute)

即下图示例位置:

根据前面的解释,调用getRoutes的逻辑会遍历所有的RouteLocator,然后在RouteLocator内部又会遍历调用所有的RouteDefinitionLocator。此时就到了最关键的convertToRoute方法。该方法就会构建所有的断路器、拦截器(实例化Bean的时候有传递对应的Factory,在此处完成构建)

最终lookupRoute的返回值是Mono<Route>对象,即会返回匹配到的第一个Route,该对象就是我们预期命中的Route


3、实例化WebHandler

在getHandlerInternal方法(org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#getHandlerInternal)中

,在命中Route路由后,只是将Route放置在了上下文中,此处真正返回的是在该HandlerMapping中定义的WebHandler对象

WebHandler对象同样是在自动装配类中实例化的,并且附带过滤器配置。

于是,我们可以得到结论,在最后传递到HandlerAdapter中执行handler请求的Object对象就是Gateway中定义的FilteringWebHandler(WebHandler)

最终在该WebHandler中,完成对应的执行逻辑。查看代码可以得出结论,这里的Filter主要分为两部分,一部分是从Route对象上获取的Filter(GatewayFilter),还有一部分是全局Filter(GlobalFilter),这部分在该WebHandler实例化的就传递进来了。将所有的Filter串联在一起,以一个责任链的形式完成业务逻辑




三、实践业务扩展点

1、定义扩展Route对象

我们可以自定义一个RouteDefinitionLocator类型的Bean,只要在调用该类的getRouteDefinitions方法时,返回我们自定义的Route,具体定义方式有很多,比如使用JSON,只需要最终解析出来的逻辑满足RouteDefinition规范即可


2、Filter能做什么

更多请参考官网文档

常见GlobalFilter:

  1. AdaptCachedBodyGlobalFilter:缓存
  2. NettyWriteResponseFilter:response写入
  3. ForwardPathFilter:转发请求
  4. ReactiveLoadBalancerClientFilter:负载均衡
  5. LoadBalancerServiceInstanceCookieFilter:根据cookie负载均衡
  6. NettyRoutingFilter:利用netty发起请求

常见GatewayFilter:

  1. AddRequestHeaderGatewayFilter:添加请求头
  2. PrefixPathGatewayFilter:切除path前缀
  3. RewritePathGatewayFilter:重写path
  4. RetryGatewayFilter:请求重试
  5. RemoveRequestHeaderGatewayFilter:移除请求头信息
  6. RequestRateLimiterGatewayFilter:限流

业务系统中常见的使用:

  1. 认证授权
  2. 基础参数校验
  3. 消息结构体的转化(XML转JSON)
  4. 业务参数级别的限流(尽管自带限流,但是功能过于简单,无法满足企业级系统的能力)
  5. 配置参数完成请求参数和返回结果的二次修改
  6. RPC的泛化调用
  7. 业务指标的监控埋点

3、定义扩展Filter对象

如果我们希望这个Filter不用配置全局生效,就继承GlobalFilter;如果是希望在RouteDefinition中定义了才生效,则继承GatewayFilter。

补充:

  1. 针对GatewayFilter,我们要定义的有两部分,一个是Filter的定义AbstractGatewayFilterFactory类(Filter的Factory),需要实现apply方法用于实例化相应的GatewayFilter对象(可使用OrderedGatewayFilter对象进行包装);另一个是Filter的实现类,用于完成具体的业务逻辑。
  2. 执行流程为,在Route实例化的时候(convertToRoute),会根据Route中配置的Filter列表名字过滤(不修改则使用默认规则)出后所有的GatewayFilterFactory,然后调用GatewayFilterFactory的apply方法,此时就会回调到我们创建的GatewayFilter类的apply方法,此时只需要在apply方法中调用我们真正的业务Filter即可(尽管这里是Factory,但是可以参考适配器模式理解),示例代码如下:
@Component
public class RequestHandlerGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestHandlerGatewayFilterConfig> {
    @Autowired
    RequestHandlerGatewayFilter filter;

    public RequestHandlerGatewayFilterFactory() {
        // 指定配置文件对象类型
        super(RequestHandlerGatewayFilterConfig.class);
    }

    @Override
    public GatewayFilter apply(RequestHandlerGatewayFilterConfig config) {
        // 设置filter顺序
        int order = Optional.ofNullable(config)
                .map(BaseFilterConfig::getOrder)
                .orElse(RouteOrderConstants.REQUEST_HANDLER_ORDER);

        return new OrderedGatewayFilter((exchange, chain) -> {
            // 配置文件内容传递
            exchange.getAttributes().put(GatewayConstants.CACHE_REQUEST_REQUEST_HANDLER_CONFIG, config);
            return filter.apply(exchange, chain);
        }, order);
    }
}

4、定义父类Filter简化请求参数处理

GatewayFilter的filter方法参数为ServerWebExchange对象,这并不方便我们在业务上对请求参数处理。我们可以编写一个抽象父类,用于屏蔽掉请求参数获取这部分,示例代码可参考:

public class CacheOutputMessage implements ReactiveHttpOutputMessage {

    private final DataBufferFactory bufferFactory;

    private final HttpHeaders httpHeaders;

    private Flux<DataBuffer> body = Flux.error(new IllegalStateException("error"));

    public CacheOutputMessage(ServerWebExchange exchange, HttpHeaders httpHeaders) {
        this.bufferFactory = exchange.getResponse().bufferFactory();
        this.httpHeaders = httpHeaders;
    }

    @Override
    public void beforeCommit(Supplier<? extends Mono<Void>> action) {
    }

    @Override
    public boolean isCommitted() {
        return false;
    }

    @Override
    public HttpHeaders getHeaders() {
        return this.httpHeaders;
    }

    @Override
    public DataBufferFactory bufferFactory() {
        return this.bufferFactory;
    }
    
    
    public Flux<DataBuffer> getBody() {
        return this.body;
    }

    @Override
    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
        this.body = Flux.from(body);
        return Mono.empty();
    }

    @Override
    public Mono<Void> writeAndFlushWith(
            Publisher<? extends Publisher<? extends DataBuffer>> body) {
        return writeWith(Flux.from(body)
                .flatMap(p -> p));
    }

    @Override
    public Mono<Void> setComplete() {
        return writeWith(Flux.empty());
    }
}
public class AbstractGatewayFilter {

    /**
     * 入口:FilterFactory的回调方法
     */
    public Mono<Void> apply(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 如果已经完成请求,不再执行后续操作
        if (ServerWebExchangeUtils.isAlreadyRouted(exchange)) {
            return chain.filter(exchange);
        }

        // 1、修改请求体
        ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
        Mono<String> afterModifiedBody = serverRequest.bodyToMono(String.class)
                .defaultIfEmpty("")
                .flatMap(body -> {
                    BiFunction<ServerWebExchange, Mono<String>, Mono<String>> modifyBodyFun =
                            (funExchange, funBody) ->
                                    // 修改请求内容
                                    funBody.map(b -> getModifiedBody(b, exchange));
                    return modifyBodyFun.apply(exchange, Mono.just(body));
                });

        // 2、重新构造请求对象(含有请求头修改)
        return prepareOutputMessage(exchange, afterModifiedBody)
                .flatMap(outputMessage -> chain.filter(
                        exchange.mutate().request(
                                decorate(exchange, outputMessage.getHeaders(), outputMessage)
                        ).build()
                ));
    }

    private Mono<CacheOutputMessage> prepareOutputMessage(ServerWebExchange exchange, Mono<String> modifiedBody) {
        HttpHeaders headers = copyHeaders(exchange.getRequest().getHeaders());
        BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter
                = BodyInserters.fromPublisher(modifiedBody, String.class);

        CacheOutputMessage outputMessage = new CacheOutputMessage(exchange, headers);
        return bodyInserter.insert(outputMessage, new BodyInserterContext())
                .thenReturn(outputMessage);
    }

    private ServerHttpRequestDecorator decorate(ServerWebExchange exchange,
                                                HttpHeaders headers,
                                                CacheOutputMessage outputMessage) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
            @Override
            public HttpHeaders getHeaders() {
                return copyHeaders(getModifiedHeaders(exchange, super.getHeaders()));
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }
        };
    }

    /**
     * 复制一份新的请求头
     */
    private HttpHeaders copyHeaders(HttpHeaders headers) {
        HttpHeaders newHeaders = new HttpHeaders();
        if (headers == null) {
            return newHeaders;
        }
        Map<String, List<String>> map = new HashMap<>();
        headers.forEach((key, dataList) ->
                map.put(key,
                        CollectionUtils.isEmpty(dataList) ? dataList : new ArrayList<>(dataList))
        );
        newHeaders.putAll(map);
        return newHeaders;
    }
    
    /**
     * 子类可重写此方法,用于修改请求头
     */
    protected HttpHeaders getModifiedHeaders(ServerWebExchange exchange, HttpHeaders headers) {
        return headers;
    }

    /**
     * 子类可重写此方法,用于修改请求内容
     */
    protected StringtModifiedBody(String body, ServerWebExchange exchange) {
        return body;
    }
}

相关文章:

  • 【算法进阶详解 第一节】树状数组
  • 人工智能在文化遗产保护中的创新:科技与文化的完美融合
  • Redis离线安装
  • springboot项目如何部署到tomcat中
  • 深度学习算法:开启智能时代的钥匙
  • 前端为什么要使用new Promise包裹一个函数
  • 联合概率:定义、公式和示例
  • CRISPR spacers数据库;CRT和PILER-CR用于MAGs的spacers搜索
  • 强化学习-策略梯度算法
  • 复旦:LLM知识问答任务性能预测
  • 【第13章:自监督学习与少样本学习—13.4 自监督学习与少样本学习的未来研究方向与挑战】
  • Spring Boot02(数据库、Redis)---java八股
  • 利用xtquant高效获取财务数据:量化分析的重要补充
  • Python 注解字典操作秘籍:从入门到精通
  • vue3.x的toRefs详细解读以及示例
  • 【第13章:自监督学习与少样本学习—13.1 自监督学习最新进展与实现方法】
  • Java 实现 Redis中的GEO数据结构
  • 基于 Python 和 OpenCV 的酒店客房入侵检测系统设计与实现
  • 服务网格(Istio)核心概念与关键知识点
  • Redis未授权访问漏洞导致getshell
  • 农林生物安全全国重点实验室启动建设,聚焦重大有害生物防控等
  • 哈尔滨工业大学原副校长王魁业逝世,享年92岁
  • 2025上海十大动漫IP评选活动启动
  • 巴基斯坦对印度发起网络攻击,致其约70%电网瘫痪
  • 上财发布“AI+课程体系”,人工智能如何赋能财经教育?
  • 人民日报刊文:守护“技术进步须服务于人性温暖”的文明底线