Spring Cloud Gateway 源码剖析之Route数据模型(1)

二、Route 构建方式


一般构建分为两种:外部化配置和编程方式

1、外部化配置

spring:

cloud:

gateway:

routes:

  • id: after_route // ①

uri: https://example.org // ②

predicates:

  • Cookie=mycookie,mycookievalue // ③

filters:

  • AddRequestHeader=X-Request-Foo, Bar // ④
  • ① 配置了一个 Route id 为 after_route

  • ② 客户端请求转发的目的地:https://example.org

  • ③ 在 request 中,当存在名字 mycookie 的 cookie 的值匹配 mycookievalue 则算成功

  • ④ 定义了一个 Filter,匹配成功后,会在请求头上添加 X-Request-Foo:Bar

2、编程方式

用上面的例子,转换成编程方式

@Bean

public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {

builder.routes()

.route(r -> r.cookie(“mycookie”, “mycookievalue”)

.filters(f -> f.addRequestHeader(“X-Request-Foo”, “Bar”))

.uri(“https://example.org”)

)

.build();

}

简单介绍了构建 Route 的两种方式,下面分析 Route 是如何把外部化配置与编码配置之间进行转换。

三、Route 构建原理


1、外部化配置

外部化配置是通过 GatewayProperties 进行构建的

/**

  • 网关配置信息加载

  • 从appliccation.yml中解析前缀为spring.cloud.gateway的配置

*/

@ConfigurationProperties(“spring.cloud.gateway”)

@Validated

public class GatewayProperties {

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

/**

  • 路由定义列表

  • 加载配置key=spring.cloud.gateway.routes 列表

  • List of Routes

*/

@NotNull

@Valid

private List routes = new ArrayList();

/**

  • 默认的过滤器定义列表

  • 加载配置 key = spring.cloud.gateway.default-filters 列表

  • List of filter definitions that are applied to every route.

*/

private List defaultFilters = new ArrayList();

/**

  • 网媒体类型列表

  • 加载配置 key = spring.cloud.gateway.streamingMediaTypes 列表

  • 默认包含{text/event-stream,application/stream+json}

*/

private List streamingMediaTypes;

public GatewayProperties() {

this.streamingMediaTypes = Arrays.asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON);

}

}

1.1 RouteDefinition

用来对 Route 进行定义。也就是,通过 GatewayProperties 会与外部化配置进行绑定,把外部化配置比如 properties 或者 yml 绑定到 GatewayProperties 中。

/**

  • 路由定义实体信息,包含路由的定义信息

*/

@Validated

public class RouteDefinition {

/**

  • 路由ID 编号,唯一

*/

@NotEmpty

private String id = UUID.randomUUID().toString();

/**

  • 谓语定义数组

  • predicates 属性,谓语定义数组

  • 请求通过 判断是否匹配。在 Route 里,PredicateDefinition 转换成 Predicate

*/

@NotEmpty

@Valid

private List predicates = new ArrayList();

/**

  • 过滤器定义数组

  • filters 属性,过滤器定义数组。

  • 在 Route 里,FilterDefinition 转换成 GatewayFilter

*/

@Valid

private List filters = new ArrayList();

/**

  • 路由指向的URI

*/

@NotNull

private URI uri;

/**

  • 顺序

*/

private int order = 0;

}

1.2 PredicateDefinition

/**

  • 谓语定义,在 Route 里,PredicateDefinition 将转换成 Predicate

*/

@Validated

public class PredicateDefinition {

/**

  • 谓语定义名字

  • 通过 name 对应到 org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory 的实现类。

  • 例如: name=Query 对应到 QueryRoutePredicateFactory

*/

@NotNull

private String name;

/**

  • 参数数组

  • 例如,name=Host / args={“_genkey_0” : “iocoder.cn”} ,匹配请求的 hostname 为 iocoder.cn

*/

private Map<String, String> args = new LinkedHashMap();

}

1.3 FilterDefinition

/**

  • 过滤器定义,在 Route 里,FilterDefinition将转换成 GatewayFilter

*/

@Validated

public class FilterDefinition {

/**

  • 过滤器定义名字

  • 通过 name 对应到 org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory 的实现类。

  • 例如,name=AddRequestParameter 对应到 AddRequestParameterGatewayFilterFactory

*/

@NotNull

private String name;

/**

  • 参数数组

  • 例如 name=AddRequestParameter / args={“_genkey_0”: “foo”, “_genkey_1”: “bar”} ,添加请求参数 foo 为 bar

*/

private Map<String, String> args = new LinkedHashMap();

}

1.4 RouteDefinitionLocator

在上一篇文章 Spring Cloud Gateway 源码剖析之配置初始化 中的 GatewayAutoConfiguration 核心配置类中,有提到此类。

@Bean

@Primary

public RouteDefinitionLocator routeDefinitionLocator(List routeDefinitionLocators) {

return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));

}

Gateway 提供多种方式来获取外部的配置,而 RouteDefinitionLocator 就是父接口,下面有多种实现。

在这里插入图片描述

1.4.1 PropertiesRouteDefinitionLocator

从配置文件(YML、Properties 等) 读取路由配置。代码如下:

public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {

private final GatewayProperties properties;

public PropertiesRouteDefinitionLocator(GatewayProperties properties) {

this.properties = properties;

}

public Flux getRouteDefinitions() {

// 从 GatewayProperties 获取路由配置数组。

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

}

}

1.4.2 InMemoryRouteDefinitionRepository

从存储器(内存、Redis、MySQL 等)读取、保存、删除路由配置。InMemoryRouteDefinitionRepository 是基于内存的。

public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {

/**

  • 路由配置映射 通过此来保存route

  • key :路由编号 {@link RouteDefinition#id}

*/

private final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap());

public InMemoryRouteDefinitionRepository() {

}

public Mono save(Mono route) {

return route.flatMap(® -> {

this.routes.put(r.getId(), r);

return Mono.empty();

});

}

public Mono delete(Mono routeId) {

return routeId.flatMap((id) -> {

if (this.routes.containsKey(id)) {

this.routes.remove(id);

return Mono.empty();

} else {

return Mono.defer(() -> {

return Mono.error(new NotFoundException("RouteDefinition not found: " + routeId));

});

}

});

}

public Flux getRouteDefinitions() {

return Flux.fromIterable(this.routes.values());

}

}

基于内存,通过 Map<String, RouteDefinition> routes 来保存 Route,缺点是如果重启,那么 route 会丢失。可以实现 RouteDefinitionRepository 接口自定义比如通过 Redis、Mysql 等来保存 Route。

1.4.3 DiscoveryClientRouteDefinitionLocator

获取在注册中心的服务列表,生成对应的 RouteDefinition 数组。

public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {

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

private final DiscoveryClient discoveryClient;

private final DiscoveryLocatorProperties properties;

private final String routeIdPrefix;

private final SimpleEvaluationContext evalCtxt;

public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {

this.discoveryClient = discoveryClient;

this.properties = properties;

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

this.routeIdPrefix = properties.getRouteIdPrefix();

} else {

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

}

this.evalCtxt = SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods().build();

}

public Flux getRouteDefinitions() {

SpelExpressionParser parser = new SpelExpressionParser();

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

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

Predicate includePredicate;

if (this.properties.getIncludeExpression() != null && !“true”.equalsIgnoreCase(this.properties.getIncludeExpression())) {

includePredicate = (instance) -> {

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

return include == null ? false : include;

};

} else {

includePredicate = (instance) -> {

return true;

};

}

// 获取discoveryClient,然后发起请求

Flux var10000 = Flux.fromIterable(this.discoveryClient.getServices());

DiscoveryClient var10001 = this.discoveryClient;

var10001.getClass();

return var10000.map(var10001::getInstances).filter((instances) -> {

return !instances.isEmpty();

}).map((instances) -> {

return (ServiceInstance)instances.get(0);

}).filter(includePredicate).map((instance) -> {

String serviceId = instance.getServiceId();

RouteDefinition routeDefinition = new RouteDefinition();

// 设置 ID

routeDefinition.setId(this.routeIdPrefix + serviceId);

// 设置 uri

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

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

ServiceInstance instanceForEval = new DiscoveryClientRouteDefinitionLocator.DelegatingServiceInstance(instance, this.properties);

Iterator var8 = this.properties.getPredicates().iterator();

Iterator var11;

Entry entry;

String value;

while(var8.hasNext()) {

PredicateDefinition originalx = (PredicateDefinition)var8.next();

// 添加 path 断言

PredicateDefinition predicate = new PredicateDefinition();

predicate.setName(originalx.getName());

var11 = originalx.getArgs().entrySet().iterator();

while(var11.hasNext()) {

entry = (Entry)var11.next();

value = this.getValueFromExpr(this.evalCtxt, parser, instanceForEval, entry);

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

}

routeDefinition.getPredicates().add(predicate);

}

var8 = this.properties.getFilters().iterator();

while(var8.hasNext()) {

FilterDefinition original = (FilterDefinition)var8.next();

// 添加path 重写过滤器

FilterDefinition filter = new FilterDefinition();

filter.setName(original.getName());

var11 = original.getArgs().entrySet().iterator();

while(var11.hasNext()) {

entry = (Entry)var11.next();

value = this.getValueFromExpr(this.evalCtxt, parser, instanceForEval, entry);

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

}

routeDefinition.getFilters().add(filter);

}

return routeDefinition;

});

}

}

可以在官方的 GatewaySampleApplication 添加 Eureka 注册中心自行调试:

// 开启Eureka

@EnableDiscoveryClient

public class GatewaySampleApplication {

// … 省略其他代码

@Bean

public RouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient) {

return new DiscoveryClientRouteDefinitionLocator(discoveryClient);

}

}

当然要自己加入 Eureka 依赖以及配置文件

1.4.4 CachingRouteDefinitionLocator

public class CachingRouteDefinitionLocator implements RouteDefinitionLocator, ApplicationListener {

private final RouteDefinitionLocator delegate;

private final Flux routeDefinitions;

// 收集Route

private final Map<String, List> cache = new HashMap();

public CachingRouteDefinitionLocator(RouteDefinitionLocator delegate) {

this.delegate = delegate;

FluxCacheBuilderMapMiss var10001 = CacheFlux.lookup(this.cache, “routeDefs”, RouteDefinition.class);

RouteDefinitionLocator var10002 = this.delegate;

var10002.getClass();

this.routeDefinitions = var10001.onCacheMissResume(var10002::getRouteDefinitions);

}

public Flux getRouteDefinitions() {

return this.routeDefinitions;

}

public Flux refresh() {

this.cache.clear();

return this.routeDefinitions;

}

public void onApplicationEvent(RefreshRoutesEvent event) {

this.refresh();

}

/** @deprecated */

@Deprecated

void handleRefresh() {

this.refresh();

}

}

1.4.5 CompositeRouteDefinitionLocator

组合多种 RouteDefinitionLocator 的实现,为 RouteDefinitionRouteLocator 提供统一入口。

public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator {

// RouteDefinitionLocator 数组

private final Flux delegates;

public CompositeRouteDefinitionLocator(Flux delegates) {

this.delegates = delegates;

}

// 将组合的 delegates 的路由定义全部返回。

public Flux getRouteDefinitions() {

return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions);

}

}

到此为止外部化的配置的多种方式全部解析完毕。接下来我们来看下编程方式。

2、编程方式

// org.springframework.cloud.gateway.sample.GatewaySampleApplication

@SpringBootConfiguration

@EnableAutoConfiguration

@Import(AdditionalRoutesImportSelector.class)

public class GatewaySampleApplication {

public static final String HELLO_FROM_FAKE_ACTUATOR_METRICS_GATEWAY_REQUESTS = “hello from fake /actuator/metrics/gateway.requests”;

@Value(“${test.uri:http://httpbin.org:80}”)

String uri;

public static void main(String[] args) {

SpringApplication.run(GatewaySampleApplication.class, args);

}

@Bean

public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {

// @formatter:off

// String uri = “http://httpbin.org:80”;

// String uri = “http://localhost:9080”;

// ① RouteLocatorBuilder 可以构建多个路由信息。

return builder.routes()

// ② 指定了 Predicates,这里包含两个:

// 请求头Host需要匹配**.abc.org,通过 HostRoutePredicateFactory 产生。

// 请求路径需要匹配/anything/png,通过 PathRoutePredicateFactory 产生。

.route(r -> r.host(“**.abc.org”).and().path(“/anything/png”)

// ③ 指定了一个 Filter,下游服务响应后添加响应头X-TestHeader:foobar,

// 通过 AddResponseHeaderGatewayFilterFactory 产生。

.filters(f ->

f.prefixPath(“/httpbin”)

.addResponseHeader(“X-TestHeader”, “foobar”))

// ④ 指定路由转发的目的地 uri。

.uri(uri)

)

.route(“read_body_pred”, r -> r.host(“*.readbody.org”)

.and().readBody(String.class,

s -> s.trim().equalsIgnoreCase(“hi”))

.filters(f -> f.prefixPath(“/httpbin”)

.addResponseHeader(“X-TestHeader”, “read_body_pred”)

).uri(uri)

)

.route(“rewrite_request_obj”, r -> r.host(“*.rewriterequestobj.org”)

.filters(f -> f.prefixPath(“/httpbin”)

.addResponseHeader(“X-TestHeader”, “rewrite_request”)

.modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE,

(exchange, s) -> {

return Mono.just(new Hello(s.toUpperCase()));

})

).uri(uri)

)

.route(“rewrite_request_upper”, r -> r.host(“*.rewriterequestupper.org”)

.filters(f -> f.prefixPath(“/httpbin”)

.addResponseHeader(“X-TestHeader”, “rewrite_request_upper”)

.modifyRequestBody(String.class, String.class,

(exchange, s) -> {

return Mono.just(s.toUpperCase() + s.toUpperCase());

})

).uri(uri)

)

.route(“rewrite_response_upper”, r -> r.host(“*.rewriteresponseupper.org”)

.filters(f -> f.prefixPath(“/httpbin”)

.addResponseHeader(“X-TestHeader”, “rewrite_response_upper”)

.modifyResponseBody(String.class, String.class,

(exchange, s) -> {

return Mono.just(s.toUpperCase());

})

).uri(uri)

)

.route(“rewrite_empty_response”, r -> r.host(“*.rewriteemptyresponse.org”)

.filters(f -> f.prefixPath(“/httpbin”)

.addResponseHeader(“X-TestHeader”, “rewrite_empty_response”)

.modifyResponseBody(String.class, String.class,

(exchange, s) -> {

if (s == null) {

return Mono.just(“emptybody”);

}

return Mono.just(s.toUpperCase());

})

).uri(uri)

)

.route(“rewrite_response_fail_supplier”, r -> r.host(“*.rewriteresponsewithfailsupplier.org”)

.filters(f -> f.prefixPath(“/httpbin”)

.addResponseHeader(“X-TestHeader”, “rewrite_response_fail_supplier”)

.modifyResponseBody(String.class, String.class,

写在最后

还有一份JAVA核心知识点整理(PDF):JVM,JAVA集合,JAVA多线程并发,JAVA基础,Spring原理,微服务,Netty与RPC,网络,日志,Zookeeper,Kafka,RabbitMQ,Hbase,MongoDB,Cassandra,设计模式,负载均衡,数据库,一致性哈希,JAVA算法,数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算…

image

seHeader(“X-TestHeader”, “rewrite_response_upper”)

.modifyResponseBody(String.class, String.class,

(exchange, s) -> {

return Mono.just(s.toUpperCase());

})

).uri(uri)

)

.route(“rewrite_empty_response”, r -> r.host(“*.rewriteemptyresponse.org”)

.filters(f -> f.prefixPath(“/httpbin”)

.addResponseHeader(“X-TestHeader”, “rewrite_empty_response”)

.modifyResponseBody(String.class, String.class,

(exchange, s) -> {

if (s == null) {

return Mono.just(“emptybody”);

}

return Mono.just(s.toUpperCase());

})

).uri(uri)

)

.route(“rewrite_response_fail_supplier”, r -> r.host(“*.rewriteresponsewithfailsupplier.org”)

.filters(f -> f.prefixPath(“/httpbin”)

.addResponseHeader(“X-TestHeader”, “rewrite_response_fail_supplier”)

.modifyResponseBody(String.class, String.class,

写在最后

还有一份JAVA核心知识点整理(PDF):JVM,JAVA集合,JAVA多线程并发,JAVA基础,Spring原理,微服务,Netty与RPC,网络,日志,Zookeeper,Kafka,RabbitMQ,Hbase,MongoDB,Cassandra,设计模式,负载均衡,数据库,一致性哈希,JAVA算法,数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算…

[外链图片转存中…(img-cnB2Dqrb-1714453687779)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 19
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值