一般构建分为两种:外部化配置和编程方式
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 是如何把外部化配置与编码配置之间进行转换。
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,机器学习,云计算…
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)]