再谈SpringCloud Gateway源码

再谈SpringCloud Gateway源码

前言:

之前有阅读过一次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. 业务指标的监控埋点
  8. ...

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,但是可以参考适配器模式理解),示例代码如下:
java 复制代码
@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对象,这并不方便我们在业务上对请求参数处理。我们可以编写一个抽象父类,用于屏蔽掉请求参数获取这部分,示例代码可参考:

java 复制代码
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());
    }
}
java 复制代码
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;
    }
}
相关推荐
Tjohn91 天前
Java环境配置(JDK8环境变量配置)补充
java·开发语言
摇摆的含羞草1 天前
Java加解密相关的各种名词的含义,各种分类的算法及特点
java·开发语言·算法
huohuopro1 天前
java金额转换,将数字金额转换为7位大写
java·开发语言
lionliu05191 天前
数据库的乐观锁和悲观锁的区别
java·数据库·oracle
赵得C1 天前
2025下半年软件设计师考前几页纸
java·开发语言·分布式·设计模式·性能优化·软考·软件设计师
歪楼小能手1 天前
Android16底部导航栏添加音量加减虚拟按键
android·java·平板
高山上有一只小老虎1 天前
小红背单词
java·算法
Cosmoshhhyyy1 天前
《Effective Java》解读第26条:请不要使用原生态类型
java·开发语言
阿杆.1 天前
如何在 Spring Boot 中接入 Amazon ElastiCache
java·spring boot·后端