Spring Cloud Gateway深撸 Spring

Spring Cloud Gateway

框架 版本
Spring Boot 2.5.3
Spring Cloud 2020.0.3

maven依赖

1
2
3
4
yaml复制代码        <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

概念

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

实现方式

  1. 配置文件
1
2
3
4
5
6
7
8
9
10
yaml复制代码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类
1
2
3
4
5
6
7
8
9
10
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类型.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
java复制代码@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;
}

}
}

自定义过滤器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
java复制代码@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可以知道主要的启动配置:

1
2
3
4
5
6
7
8
9
10
11
12
properties复制代码# 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实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码        @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加载:

1
2
3
4
5
6
java复制代码        @Bean
@ConditionalOnEnabledPredicate(WeightRoutePredicateFactory.class)
public WeightCalculatorWebFilter weightCalculatorWebFilter(ConfigurationService configurationService,
ObjectProvider<RouteLocator> routeLocator) {
return new WeightCalculatorWebFilter(routeLocator, configurationService);
}

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

1
2
3
4
5
java复制代码        @Bean
public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,
RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java复制代码        @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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码        @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方法获取路由信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码
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);
}

其中核心代码:

1
java复制代码      RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());

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

1
2
3
java复制代码default String name() {
return NameUtils.normalizeRoutePredicateName(getClass());
}

通过查看实现方法发现

1
java复制代码return removeGarbage(clazz.getSimpleName().replace(RoutePredicateFactory.class.getSimpleName(), ""));

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

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

1
2
java复制代码
GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());

GatewayFilterFactory:

1
2
3
4
java复制代码default String name() {
// TODO: deal with proxys
return NameUtils.normalizeFilterFactoryName(getClass());
}

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

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%