Spring Cloud Gateway
| 框架 | 版本 |
|---|---|
| Spring Boot | 2.5.3 |
| Spring Cloud | 2020.0.3 |
maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
概念
路由(routes):gateway核心概念,包含了路由id、转发地址uri、一组断言、一组过滤器
断言(Predicate):用于断定请求是否符合当前路由规则的集合
过滤器(Filter):用来加工当前请求,其中又分为全局过滤器,和路由过滤器
实现方式
- 配置文件
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:过滤器,多个过滤器都会执行
- 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类型.
@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;
}
}
}
自定义过滤器:
@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;
}
}
}
其中AbstractRoutePredicateFactory和AbstractGatewayFilterFactory是官方提供的静态实现类,其中实现了部分通用部分,只需要写自定义的apply方法即可,其中shortcutFieldOrder就是用来处理参数注入的。
原理解析
通过以上方式,我们可以快速实现一个基于Gateway的路由服务,那么生刨一下源码,来看下它是怎么实现的:
通过查看Spring-Cloud-Gateway的源码,查看其中的spring.factories可以知道主要的启动配置:
# 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实现的
@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加载:
@Bean
@ConditionalOnEnabledPredicate(WeightRoutePredicateFactory.class)
public WeightCalculatorWebFilter weightCalculatorWebFilter(ConfigurationService configurationService,
ObjectProvider<RouteLocator> routeLocator) {
return new WeightCalculatorWebFilter(routeLocator, configurationService);
}
作为权重路由加载,因为WeightCalculatorWebFilter实现了WebFliter,而其他的路由是基于DispatcherHandler中的HandlerMapping实现的。
@Bean
public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,
RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
}
RoutePredicateHandlerMapping就是整个路由的核心处理类,其实现了HandlerMapping接口,将会被注入到DispatcherHandler的handlerMappings中发挥作用。
查看RoutePredicateHandlerMapping中,核心方法如下:
@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。
@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方法获取路由信息:
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);
}
其中核心代码:
RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());
也就是说是通过类名获取到对应的断言工厂的,而加载所有工厂的RoutePredicateFactory类中的方法:
default String name() {
return NameUtils.normalizeRoutePredicateName(getClass());
}
通过查看实现方法发现
return removeGarbage(clazz.getSimpleName().replace(RoutePredicateFactory.class.getSimpleName(), ""));
也就是说,自己实现的RoutePredicateFactory要是以RoutePredicateFactory结尾的话,就能想PathRoutePredicateFactory那样在断言里面写Path=/**然后自动找到断言实现类,不然就要自己实现getSimpleName方法。
而通过路由的Filters中的信息加载GatewayFilterFactory也是类似的实现:
GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());
GatewayFilterFactory:
default String name() {
// TODO: deal with proxys
return NameUtils.normalizeFilterFactoryName(getClass());
}
到此,基本整个Gateway的原理基本完成,还有其他功能,与主流程无关,不过可以作为扩展适配个性化的需求。