Spring Cloud Gateway深撸

1,687 阅读4分钟

Spring Cloud Gateway

框架版本
Spring Boot2.5.3
Spring Cloud2020.0.3

maven依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

概念

路由(routes):gateway核心概念,包含了路由id、转发地址uri、一组断言、一组过滤器
断言(Predicate):用于断定请求是否符合当前路由规则的集合
过滤器(Filter):用来加工当前请求,其中又分为全局过滤器,和路由过滤器

实现方式

  1. 配置文件
spring:
  cloud:
    gateway:
      routes:
        - id: theia-routes-base
          uri: "http://10.20.23.49:31002"
          predicates:
            - Path=/zhaolw01/01
          filters:
            - SetPath=/

说明:

routes:路由集合,设定路由的id、转发url、断言、过滤器
id:需要唯一,用于存储和更新路由信息
uri:转发的地址 predicates:断言集合,多个断言类型取并集 filters:过滤器,多个过滤器都会执行

  1. java类
        @Bean
        public RouteLocator theiaRouteLocator(RouteLocatorBuilder builder) {
            return builder
                    .routes()
                    .route("theiaRoute",r -> r.path("/xiangaoxiong01")
                        .filters(gatewayFilterSpec -> gatewayFilterSpec.setPath("/"))
                        .uri("http://10.20.23.49:31002")
                    )
                    .build();
        }

说明:

RouteLocator:路由的主要对象,Gateway对于此对象的加载更新,可以实现动态路由

自定义断言

通过继承AbstractRoutePredicateFactory类可以快速实现一个自定义断言,需要自定义一个Config类用于接收断言内容,重写apply方法,返回一个GatewayPredicate类型.

@Configuration
@Slf4j
public class TheiaServiceRoutePredicateFactory extends AbstractRoutePredicateFactory<TheiaServiceRoutePredicateFactory.Config> {

    public TheiaServiceRoutePredicateFactory() {
        super(Config.class);
    }

    @Bean
    @ConditionalOnEnabledPredicate
    public TheiaServiceRoutePredicateFactory getTheiaServiceRoutePredicateFactory(){
        return new TheiaServiceRoutePredicateFactory();
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("patterns");
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        List<String> patterns = config.getPatterns();
        return (GatewayPredicate) serverWebExchange -> {
            ServerHttpRequest request = serverWebExchange.getRequest();
            log.info("自定义断言:{}", patterns);
            String url = request.getURI().getRawPath();
            return patterns.parallelStream().filter(x -> url.startsWith(x)).count() > 0 ;
        };
    }

    @Validated
    public static class Config {

        private List<String> patterns = new ArrayList<>();

        public List<String> getPatterns() {
            return patterns;
        }

        public TheiaServiceRoutePredicateFactory.Config setPatterns(List<String> patterns) {
            this.patterns = patterns;
            return this;
        }

    }
}

自定义过滤器:

@Configuration
@Slf4j
public class TheiaServiceGatewayFilterFactory extends AbstractGatewayFilterFactory<TheiaServiceGatewayFilterFactory.Config> {

    public TheiaServiceGatewayFilterFactory() {
        super(TheiaServiceGatewayFilterFactory.Config.class);
    }

    @Bean
    @ConditionalOnEnabledFilter
    public TheiaServiceGatewayFilterFactory getTheiaServiceGatewayFilterFactory() {
        return new TheiaServiceGatewayFilterFactory();
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("template");
    }


    @Override
    public GatewayFilter apply(Config config) {
        String template = config.getTemplate();
        return (exchange, chain) -> {
            ServerHttpRequest req = exchange.getRequest();
            log.info("自定义过滤器:{}",template);
            String newPath = req.getURI().getRawPath().replaceAll(template,"/");
            ServerHttpRequest request = req.mutate().path(newPath).build();
            return chain.filter(exchange.mutate().request(request).build());
        };
    }

    public static class Config {

        private String template;

        public String getTemplate() {
            return template;
        }

        public void setTemplate(String template) {
            this.template = template;
        }

    }
}

其中AbstractRoutePredicateFactoryAbstractGatewayFilterFactory是官方提供的静态实现类,其中实现了部分通用部分,只需要写自定义的apply方法即可,其中shortcutFieldOrder就是用来处理参数注入的。

原理解析

通过以上方式,我们可以快速实现一个基于Gateway的路由服务,那么生刨一下源码,来看下它是怎么实现的: 通过查看Spring-Cloud-Gateway的源码,查看其中的spring.factories可以知道主要的启动配置:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayResilience4JCircuitBreakerAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\
org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration,\
org.springframework.cloud.gateway.config.SimpleUrlHandlerMappingGlobalCorsAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayReactiveOAuth2AutoConfiguration

通过查看GatewayClassPathWarningAutoConfiguration,可以看出Gateway是基于WebFlux实现的

        @Configuration(proxyBeanMethods = false)
   @ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet")
   @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
   protected static class SpringMvcFoundOnClasspathConfiguration {

      public SpringMvcFoundOnClasspathConfiguration() {
         throw new MvcFoundOnClasspathException();
      }

   }

   @Configuration(proxyBeanMethods = false)
   @ConditionalOnMissingClass("org.springframework.web.reactive.DispatcherHandler")
   protected static class WebfluxMissingFromClasspathConfiguration {

      public WebfluxMissingFromClasspathConfiguration() {
         log.warn(BORDER + "Spring Webflux is missing from the classpath, "
               + "which is required for Spring Cloud Gateway at this time. "
               + "Please add spring-boot-starter-webflux dependency." + BORDER);
      }

   }

关键点在于DispatcherServlet是基于Servlet实现的,当有这个Class加载的时候就会报错,当没有DispatcherHandler存在的时候就会报警告异常。

继续往下看GatewayAutoConfiguration就是我们要看的核心了,由于内容过多,只看其中比较重要的几个Bean加载:

        @Bean
   @ConditionalOnEnabledPredicate(WeightRoutePredicateFactory.class)
   public WeightCalculatorWebFilter weightCalculatorWebFilter(ConfigurationService configurationService,
         ObjectProvider<RouteLocator> routeLocator) {
      return new WeightCalculatorWebFilter(routeLocator, configurationService);
   }

作为权重路由加载,因为WeightCalculatorWebFilter实现了WebFliter,而其他的路由是基于DispatcherHandler中的HandlerMapping实现的。

        @Bean
   public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,
         RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
      return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
   }

RoutePredicateHandlerMapping就是整个路由的核心处理类,其实现了HandlerMapping接口,将会被注入到DispatcherHandlerhandlerMappings中发挥作用。

查看RoutePredicateHandlerMapping中,核心方法如下:

        @Override
   protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
      // don't handle requests on management port if set and different than server port
      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)
            // .log("route-predicate-handler-mapping", Level.FINER) //name this
            .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);
               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) + "]");
               }
            })));
   }

此方法在DispatcherHandler的主方法中会调用,其中方法lookupRoute就会通过断言规则return r.getPredicate().apply(exchange),获取对应的Route返回,然后再后续的FilteringWebHandler中通过GATEWAY_ROUTE_ATTR属性获取Route对象,然后执行对象中的所有Filters

        @Override
   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);
      // TODO: needed or cached?
      AnnotationAwareOrderComparator.sort(combined);

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

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

前面的流程基本说明的整个Gateway的基础工作原理,而通过前面的逻辑,可以看到主要是通过RouteLocator类的getRoutes方法获取路由信息:


    private AsyncPredicate<ServerWebExchange> lookup(RouteDefinition route, PredicateDefinition predicate) {
      RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());
      if (factory == null) {
         throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName());
      }
      if (logger.isDebugEnabled()) {
         logger.debug("RouteDefinition " + route.getId() + " applying " + predicate.getArgs() + " to "
               + predicate.getName());
      }

      // @formatter:off
      Object config = this.configurationService.with(factory)
            .name(predicate.getName())
            .properties(predicate.getArgs())
            .eventFunction((bound, properties) -> new PredicateArgsEvent(
                  RouteDefinitionRouteLocator.this, route.getId(), properties))
            .bind();
      // @formatter:on

      return factory.applyAsync(config);
   }

其中核心代码:

      RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());

也就是说是通过类名获取到对应的断言工厂的,而加载所有工厂的RoutePredicateFactory类中的方法:

default String name() {
      return NameUtils.normalizeRoutePredicateName(getClass());
   }

通过查看实现方法发现

return removeGarbage(clazz.getSimpleName().replace(RoutePredicateFactory.class.getSimpleName(), ""));

也就是说,自己实现的RoutePredicateFactory要是以RoutePredicateFactory结尾的话,就能想PathRoutePredicateFactory那样在断言里面写Path=/**然后自动找到断言实现类,不然就要自己实现getSimpleName方法。

而通过路由的Filters中的信息加载GatewayFilterFactory也是类似的实现:


         GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());

GatewayFilterFactory:

default String name() {
      // TODO: deal with proxys
      return NameUtils.normalizeFilterFactoryName(getClass());
   }

到此,基本整个Gateway的原理基本完成,还有其他功能,与主流程无关,不过可以作为扩展适配个性化的需求。