spring cloud gateway 啟動流程及原理分析


 原文鏈接:https://blog.csdn.net/qq_37616173/article/details/83790842


 

網關的包結構

 

 

 

actuate中定義了一個叫GatewayControllerEndpoint的類,這個類提供一些對外的接口,可以獲取網關的一些信息,比如路由的信息,改變路由地址等等。

config中定義了一些啟動時去加載的類,配置路由信息和讀取你的配置文件就在這里完成。

discovery中定義了注冊中心相關的內容,包括注冊中心的路由等。

event定義了一些事件他們都繼承自ApplicationEvent,對事件發布不了解的可以去看看spring的代碼。

filter中定義了spring cloud gateway實現的一些過濾器。

handler中定義了很多Predicate相關的Factory

route就是我們路由的相關

support是工具包等。

 


 

 

啟動流程

網關啟動第一步加載的就是去加載config包下的幾個類。

這幾個類就定義了網關需要加載的配置項。

在我第一次做網關開發的時候我引入了spring-boot-starter-web的依賴,這樣是會報錯的,因為gateway是基於spring-webflux開發的,他依賴的DispatcherHandler就和我們web里的DispatcherServlet一樣的功能。

 

1、加載GatewayClassPathWarningAutoConfiguration這個類,就指明了我們需要什么不需要什么,他加載於GatewayAutoConfiguration之前,如果DispatcherServlet存在,就會給與警告,同樣的DispatcherHandler不存在也會警告。

@Configuration
@AutoConfigureBefore(GatewayAutoConfiguration.class)
public class GatewayClassPathWarningAutoConfiguration {

    private static final Log log = LogFactory
            .getLog(GatewayClassPathWarningAutoConfiguration.class);

    private static final String BORDER = "\n\n**********************************************************\n\n";

    @Configuration
    @ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet")
    protected static class SpringMvcFoundOnClasspathConfiguration {

        public SpringMvcFoundOnClasspathConfiguration() {
            log.warn(BORDER
                    + "Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. "
                    + "Please remove spring-boot-starter-web dependency." + BORDER);
        }

    }

    @Configuration
    @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);
        }

    }

}

 

 

2、它加載完成之后加載的是GatewayLoadBalancerClientAutoConfiguration這個是gateway負載均衡的過濾器實現的加載,他將LoadBalancerClientFilter 注入到了容器中,這個過濾器后面再說。

@Configuration
@ConditionalOnClass({ LoadBalancerClient.class, RibbonAutoConfiguration.class,
        DispatcherHandler.class })
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
public class GatewayLoadBalancerClientAutoConfiguration {

    // GlobalFilter beans

    @Bean
    @ConditionalOnBean(LoadBalancerClient.class)
    @ConditionalOnMissingBean(LoadBalancerClientFilter.class)
    public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client,
            LoadBalancerProperties properties) {
        return new LoadBalancerClientFilter(client, properties);
    }

}

 

 

3、之后便是我們的GatewayAutoConfiguration正式加載了,這個里面定義了非常多的內容,我們大部分用到的過濾器,過濾器工廠都是在這里構建的。包括之前的gatewayControllerEndpoint也是在這里注入容器中的。這個類的定義很長,我就不再這里都放了,列舉幾個。

@Configuration

//開啟網關,不寫默認為true

@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)

@EnableConfigurationProperties

@AutoConfigureBefore(HttpHandlerAutoConfiguration.class)

@AutoConfigureAfter({GatewayLoadBalancerClientAutoConfiguration.class, GatewayClassPathWarningAutoConfiguration.class})

@ConditionalOnClass(DispatcherHandler.class)

public class GatewayAutoConfiguration {

 @Configuration

 @ConditionalOnClass(HttpClient.class)

 protected static class NettyConfiguration {

  @Bean

  @ConditionalOnMissingBean

  public HttpClient httpClient(@Qualifier("nettyClientOptions") Consumer<? super HttpClientOptions.Builder> options) {

   return HttpClient.create(options);

  }

  ... //還有很多 這里不列舉了。我們都知道spring cloud 是基於Netty實現的,這里他這個靜態內部類就是初始化netty需要的東西。

}

//初始化了加載配置文件的對象,建立route

@Bean

 public GatewayProperties gatewayProperties() {

  return new GatewayProperties();

 }

//初始化請求轉發 過濾器

@Bean

 @ConditionalOnProperty(name = "spring.cloud.gateway.forwarded.enabled", matchIfMissing = true)

 public ForwardedHeadersFilter forwardedHeadersFilter() {

  return new ForwardedHeadersFilter();

 }

 //最后初始化gatewayControllerEndpoint 這里注意只有引入spring-boot-starter-actuator他才會加載

@Configuration

 @ConditionalOnClass(Health.class)

 protected static class GatewayActuatorConfiguration {

  @Bean

  @ConditionalOnEnabledEndpoint

  public GatewayControllerEndpoint gatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters,

                List<GatewayFilterFactory> GatewayFilters, RouteDefinitionWriter routeDefinitionWriter,

                RouteLocator routeLocator) {

   return new GatewayControllerEndpoint(routeDefinitionLocator, globalFilters, GatewayFilters, routeDefinitionWriter, routeLocator);

  }

 }

 

4、我們通過注冊中心發現的路由不是在config包下定義的而是在discovery包下GatewayDiscoveryClientAutoConfiguration實現了從注冊中心發現內容

@Configuration

@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)

@AutoConfigureBefore(GatewayAutoConfiguration.class)

@ConditionalOnClass({DispatcherHandler.class, DiscoveryClient.class})

@EnableConfigurationProperties

public class GatewayDiscoveryClientAutoConfiguration {

 @Bean

 @ConditionalOnBean(DiscoveryClient.class)

 @ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")

 public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(

   DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {

  return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);

 }

 @Bean public DiscoveryLocatorProperties discoveryLocatorProperties() {

  DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();

  properties.setPredicates(initPredicates());

  properties.setFilters(initFilters());

  return properties;

 }

}
View Code

 

 

5、這些注冊完畢后,我們的配置文件就開始讀取了,這兩個中定義了我們配置文件的讀取規則,其中DiscoveryLocatorProperties都有默認的值,我們可以不用關心。GatewayProperties比較重要,這里就定義了我們配置文件里的所有路由信息,讀取完成后,接下來就要變成真正的路由信息了。

@ConfigurationProperties("spring.cloud.gateway")
@Validated
public class GatewayProperties {

    private final Log logger = LogFactory.getLog(getClass());

    /**
     * List of Routes.
     */
    @NotNull
    @Valid
    private List<RouteDefinition> routes = new ArrayList<>();

    /**
     * List of filter definitions that are applied to every route.
     */
    private List<FilterDefinition> defaultFilters = new ArrayList<>();

    private List<MediaType> streamingMediaTypes = Arrays
            .asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON);

    public List<RouteDefinition> getRoutes() {
        return routes;
    }

    public void setRoutes(List<RouteDefinition> routes) {
        this.routes = routes;
        if (routes != null && routes.size() > 0 && logger.isDebugEnabled()) {
            logger.debug("Routes supplied from Gateway Properties: " + routes);
        }
    }

    public List<FilterDefinition> getDefaultFilters() {
        return defaultFilters;
    }

    public void setDefaultFilters(List<FilterDefinition> defaultFilters) {
        this.defaultFilters = defaultFilters;
    }

    public List<MediaType> getStreamingMediaTypes() {
        return streamingMediaTypes;
    }

    public void setStreamingMediaTypes(List<MediaType> streamingMediaTypes) {
        this.streamingMediaTypes = streamingMediaTypes;
    }

    @Override
    public String toString() {
        return "GatewayProperties{" + "routes=" + routes + ", defaultFilters="
                + defaultFilters + ", streamingMediaTypes=" + streamingMediaTypes + '}';
    }

}

 

 

這里就要說一下 RouteLocator接口,這個接口提供了一個getRoutes()方法返回一個Flux所以,他的實現中就定義了讀取配置文件轉成路由的關鍵。

public interface RouteLocator {

 Flux<Route> getRoutes();

}

 

我們先看從配置文件中加載的路由信息也就是他的實現RouteDefinitionRouteLocator

 

public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAware, ApplicationEventPublisherAware {

 protected final Log logger = LogFactory.getLog(getClass());

 private final RouteDefinitionLocator routeDefinitionLocator;

 private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>();

 private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>();

 private final GatewayProperties gatewayProperties;

 private final SpelExpressionParser parser = new SpelExpressionParser();

 private BeanFactory beanFactory;

 private ApplicationEventPublisher publisher;

 public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,

            List<RoutePredicateFactory> predicates,

            List<GatewayFilterFactory> gatewayFilterFactories,

            GatewayProperties gatewayProperties) {

  this.routeDefinitionLocator = routeDefinitionLocator;

  initFactories(predicates);

  gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));

  this.gatewayProperties = gatewayProperties;

 }

  ...

}

 

這個類的一個屬性routeDefinitionLocator中已經定義了我們的路由,只不過是代理對象。 這個屬性所對應的接口RouteDefinitionLocator有很多種實現,現在看PropertiesRouteDefinitionLocator,

他的構造方法讀取了GatewayProperties ,所以到這里我們的路由就已經存在了。通過他的getRoutes()方法,我們就能方便的取出所有定義的路由信息了

public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {

 private final GatewayProperties properties;

 public PropertiesRouteDefinitionLocator(GatewayProperties properties) {

  this.properties = properties;

 }

 @Override

 public Flux<RouteDefinition> getRouteDefinitions() {

  return Flux.fromIterable(this.properties.getRoutes());

 }

}

 

而 RouteDefinitionRouteLocator 的getRoutes()方法又是這樣定義的

    @Override
    public Flux<Route> getRoutes() {
        return this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute)
                // TODO: error handling
                .map(route -> {
                    if (logger.isDebugEnabled()) {
                        logger.debug("RouteDefinition matched: " + route.getId());
                    }
                    return route;
                });

        /*
         * TODO: trace logging if (logger.isTraceEnabled()) {
         * logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); }
         */
    }

 

routeDefinitionLocator.getRouteDefinitions()返回從配置文件中讀取的路由轉換成 Flux 再通過convertToRoute轉換成路由對象的數組,封裝成Flux()至此,配置文件中讀取路由信息就結束了。

接下來我們來解釋通過注冊中心的方式接受的路由信息。這里由一個叫DiscoveryClientRouteDefinitionLocator的類來實現,他同樣實現了RouteDefinitionLocator接口,能夠返回Flux,

當我們調用到getRouteDefinitions的時候,他通過discoveryClient轉換出了服務發現中心的服務路由,注意這里他沒有被轉換為Flux而是保留為Flux 這是一個坑,

我們前期很多時候去找Route發現根本就沒有從服務注冊中心拉下來的,現在才知道,他壓根就沒去轉。

 

public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {

 private final DiscoveryClient discoveryClient;

 private final DiscoveryLocatorProperties properties;

 private final String routeIdPrefix;

 public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {

// 服務發現信息 

  this.discoveryClient = discoveryClient;

 //這個配置是前面說過的DiscoveryLocatorProperties

  this.properties = properties;

  if (StringUtils.hasText(properties.getRouteIdPrefix())) {

   this.routeIdPrefix = properties.getRouteIdPrefix();

  } else {

   this.routeIdPrefix = this.discoveryClient.getClass().getSimpleName() + "_";

  }

 }

 //這個是核心

 @Override

 public Flux<RouteDefinition> getRouteDefinitions() {

  SimpleEvaluationContext evalCtxt = SimpleEvaluationContext

    .forReadOnlyDataBinding()

    .withInstanceMethods()

    .build();

  SpelExpressionParser parser = new SpelExpressionParser();

  Expression includeExpr = parser.parseExpression(properties.getIncludeExpression());

  Expression urlExpr = parser.parseExpression(properties.getUrlExpression());

  return Flux.fromIterable(discoveryClient.getServices())

    .map(discoveryClient::getInstances)

    .filter(instances -> !instances.isEmpty())

    .map(instances -> instances.get(0))

    .filter(instance -> {

     Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class);

     if (include == null) {

      return false;

     }

     return include;

    })

    .map(instance -> {

     String serviceId = instance.getServiceId();

                    RouteDefinition routeDefinition = new RouteDefinition();

                    routeDefinition.setId(this.routeIdPrefix + serviceId);

     String uri = urlExpr.getValue(evalCtxt, instance, String.class);

     routeDefinition.setUri(URI.create(uri));

     final ServiceInstance instanceForEval = new DelegatingServiceInstance(instance, properties);

     for (PredicateDefinition original : this.properties.getPredicates()) {

      PredicateDefinition predicate = new PredicateDefinition();

      predicate.setName(original.getName());

      for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {

       String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);

       predicate.addArg(entry.getKey(), value);

      }

      routeDefinition.getPredicates().add(predicate);

     }

                    for (FilterDefinition original : this.properties.getFilters()) {

                     FilterDefinition filter = new FilterDefinition();

                     filter.setName(original.getName());

      for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {

       String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);

       filter.addArg(entry.getKey(), value);

      }

      routeDefinition.getFilters().add(filter);

     }

                    return routeDefinition;

    });

 }

 

到這里,我們的路由信息就加載完畢了,網關也就啟動完成了,之后就是發送請求過濾器發功的時候了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM