原文鏈接: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; } }
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; }); }
到這里,我們的路由信息就加載完畢了,網關也就啟動完成了,之后就是發送請求過濾器發功的時候了。