Route
加载
网关服务核心功能是路由转发,即将接收的请求如何正确的路由到下层具体的服务模块。下面分析下这些路由信息构建的流程。
路由信息配置文件:
代码语言:javascript复制spring:
cloud:
gateway:
routes:
- id: cloud-oauth2
uri: lb://cloud-oauth2
order: 8001
predicates:
- Path=/cloud-oauth2/**
filters:
- StripPrefix=1
- id: cloud-biz
uri: lb://cloud-biz
order: 8003
predicates:
- Path=/cloud-biz/**
filters:
- StripPrefix=1
上面就是包含有两条路由信息的配置文件,Gateway
将其加载解析最终在内存中的数据结构Route
:
public class Route implements Ordered {
/**
* 路由编号
* ID 编号,唯一
*/
private final String id;
/**
* 路由目的 URI
*
*/
private final URI uri;
/**
* 顺序
* 当请求匹配到多个路由时,使用顺序小的
*/
private final int order;
/**
* 谓语数组
* 请求通过 predicates 判断是否匹配
*/
private final Predicate<ServerWebExchange> predicate;
/**
* 过滤器数组
*/
private final List<GatewayFilter> gatewayFilters;
}
由代码可以看到一个路由应该包含如下必要的信息:
- id:路由编号,唯一
- uri:路由向的 URI,对应的具体业务服务的URL
- order:顺序,当请求匹配多个路由时,使用顺序小的
- predicate: 请求匹配路由的断言条件
- gatewayFilters: 当前路由上存在的过滤器,用于对请求做拦截处理
流程分析
1、路由配置加载
通过@ConfigurationProperties("spring.cloud.gateway")
配注解将配置文件中路由规则信息加载到GatewayProperties
对象中,其中路由信息会被解析成RouteDefinition
结构。
@ConfigurationProperties("spring.cloud.gateway")
@Validated
public class GatewayProperties {
/**
* 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);
}
路由定义RouteDefinition
代码见下:
/**
* 路由定义实体信息,包含路由的定义信息
* @author Spencer Gibb
*/
@Validated
public class RouteDefinition {
/**
* 路由ID 编号,唯一
*/
@NotEmpty
private String id = UUID.randomUUID().toString();
/**
* 谓语定义数组
* predicates 属性,谓语定义数组
* 请求通过 predicates 判断是否匹配。在 Route 里,PredicateDefinition 转换成 Predicate
*/
@NotEmpty
@Valid
private List<PredicateDefinition> predicates = new ArrayList<>();
/**
*过滤器定义数组
* filters 属性,过滤器定义数组。
* 在 Route 里,FilterDefinition 转换成 GatewayFilter
*/
@Valid
private List<FilterDefinition> filters = new ArrayList<>();
/**
* 路由指向的URI
*/
@NotNull
private URI uri;
/**
* 顺序
*/
private int order = 0;
}
结构比较简单,和文件中的配置是一一对应的,其中包含了两个集合分别用于存储路由断言器的Definition
和路由过滤器的Definition
;其中,PredicateDefinition
会转换成Predicate
,而FilterDefinition
会被转换成GatewayFilter
。
2、获取Route集合
路由配置文件已被加载到GateProperties
中,其中具体路由也被存储到RouteDefinition
中,下面看下如何进行转换。
首先,RouteRefreshListener
类监听ContextRefreshedEvent
事件,后触发RefreshRoutesEvent
事件:
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent
|| event instanceof RefreshScopeRefreshedEvent
|| event instanceof InstanceRegisteredEvent) {
reset();
}
else if (event instanceof ParentHeartbeatEvent) {
ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
resetIfNeeded(e.getValue());
}
else if (event instanceof HeartbeatEvent) {
HeartbeatEvent e = (HeartbeatEvent) event;
resetIfNeeded(e.getValue());
}
}
private void reset() {
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
Tips:
ContextRefreshedEvent
是在ApplicationContext.refresh()
执行完成后触发,即Context
初始化全部完成。
WeightCalculatorWebFilter
类监听到RefreshRoutesEvent
事件,触发调用CachingRouteLocator#getRoutes()
获取Route
集合:
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof PredicateArgsEvent) {
handle((PredicateArgsEvent) event);
}
else if (event instanceof WeightDefinedEvent) {
addWeightConfig(((WeightDefinedEvent) event).getWeightConfig());
}
//routeLocator内部包装的就是CachingRouteLocator实例
else if (event instanceof RefreshRoutesEvent && routeLocator != null) {//监听RefreshRoutesEvent
routeLocator.ifAvailable(locator -> locator.getRoutes().subscribe()); //CachingRouteLocator中routes被真正初始化
}
}
CachingRouteLocator#getRoutes()
方法只是简单返回内部变量routes
:
public Flux<Route> getRoutes() {
return this.routes;
}
3、routes
初始化流程
routes
在构造方法中进行初始化:
public CachingRouteLocator(RouteLocator delegate) {
this.delegate = delegate;
routes = CacheFlux.lookup(cache, "routes", Route.class)
.onCacheMissResume(() -> this.delegate.getRoutes()//
.sort(AnnotationAwareOrderComparator.INSTANCE));//sort
}
Tips:构造方法中初始化
routes
,由于是异步的这时并没有真正的触发底层执行,只有在调用locator.getRoutes()
真正使用到routes
时才会触发底层调用。所以,WeightCalculatorWebFilter
中监听事件调用locator.getRoutes()
就是触发执行。
从代码看,delegate
代理具体类型是CachingRouteLocator
-> CompositeRouteLocator
,CompositeRouteLocator
,汇聚了所有的RouteLocator
集合,主要包含两类:一类是RouteDefinitionRouteLocator
,基于RouteDefinition
获取Route
;另一类是编程方式自定义创建RouteLocator
,如:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.host("**.abc.org").and().path("/image/png")
.filters(f ->
f.addResponseHeader("X-TestHeader", "foobar"))
.uri("http://httpbin.org:80")
)
.build();
}
这里我们主要分析RouteDefinition
转Route
流程,所以需要关注RouteDefinitionRouteLocator#getRoutes
:
public Flux<Route> getRoutes() {
//routeDefinitionLocator即是CompositeRouteDefinitionLocator实例
return this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute)
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition matched: " route.getId());
}
return route;
});
}
routeDefinitionLocator
是一个CompositeRouteDefinitionLocator
类型实例,将InMemoryRouteDefinitionRepository
和PropertiesRouteDefinitionLocator
包装混合到一起。
public Flux<RouteDefinition> getRouteDefinitions() {
return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions);
}
Tips:这里涉及的各种代理关系后续分析自动装配类
GatewayAutoConfiguration
再来细说。
routeDefinitionLocator.getRouteDefinitions()
实际上调用InMemoryRouteDefinitionRepository
和PropertiesRouteDefinitionLocator
的getRouteDefinitions
方法获取到RouteDefinition
集合,然后执行convertToRoute()
方法将RouteDefinition
转成Route
对象。
private Route convertToRoute(RouteDefinition routeDefinition) {
//解析Predicate,将PredicateDefinition转出Predicate
AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
//解析GatewayFilter,将FilterDefinition转成GatewayFilter,包括配置中定义的默认Filter和直接配置的Filter,不包括全局过滤器
List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);
return Route.async(routeDefinition).asyncPredicate(predicate)
.replaceFilters(gatewayFilters).build();
}
这里主要关注下getFilters(routeDefinition)
如何将FilterDefinition
转成GatewayFilter
。
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters
:
private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) {
List<GatewayFilter> filters = new ArrayList<>();
// 加载default filter
if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {
filters.addAll(loadGatewayFilters(DEFAULT_FILTERS,
this.gatewayProperties.getDefaultFilters()));
}
// 加载指定filter
if (!routeDefinition.getFilters().isEmpty()) {
filters.addAll(loadGatewayFilters(routeDefinition.getId(),
routeDefinition.getFilters()));
}
//对Filter进行排序
AnnotationAwareOrderComparator.sort(filters);
return filters;
}
接下来才是真正进行转换的逻辑,org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters
:
List<GatewayFilter> loadGatewayFilters(String id,
List<FilterDefinition> filterDefinitions) {
//遍历Route的filterDefinitions,将过滤器定义转换成对应的过滤器
ArrayList<GatewayFilter> ordered = new ArrayList<>(filterDefinitions.size());
for (int i = 0; i < filterDefinitions.size(); i ) {
FilterDefinition definition = filterDefinitions.get(i);
//获取匹配的GatewayFilterFactory
GatewayFilterFactory factory = this.gatewayFilterFactories
.get(definition.getName());
if (factory == null) {
throw new IllegalArgumentException(
"Unable to find GatewayFilterFactory with name "
definition.getName());
}
//获取参数,格式如:_genkey_0:1,_genkey_1:2格式,对配置中参数使用逗号分隔
Map<String, String> args = definition.getArgs();
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition " id " applying filter " args " to "
definition.getName());
}
//参数解析,解析出参数名称和解析支持SpEL表达式值#{}
Map<String, Object> properties = factory.shortcutType().normalize(args,
factory, this.parser, this.beanFactory);
//创建Configuration实例,这个是GatewayFilterFactory实现类中指定的,如:
/*
public StripPrefixGatewayFilterFactory() {
super(Config.class);//这里指定Configuration的Class
}
*/
Object configuration = factory.newConfig();
//通过反射将解析的参数设置到创建的Configuration实例中
ConfigurationUtils.bind(configuration, properties,
factory.shortcutFieldPrefix(), definition.getName(), validator);
// some filters require routeId
// TODO: is there a better place to apply this?
if (configuration instanceof HasRouteId) {
HasRouteId hasRouteId = (HasRouteId) configuration;
hasRouteId.setRouteId(id);
}
//通过过滤器工厂创建GatewayFilter
GatewayFilter gatewayFilter = factory.apply(configuration);
if (this.publisher != null) {
//发布事件
this.publisher.publishEvent(new FilterArgsEvent(this, id, properties));
}
if (gatewayFilter instanceof Ordered) {//Ordered类型的Filter直接添加
ordered.add(gatewayFilter);
}
else {//非Order类型的Filter,转成Order类型的,每个Route下从1开始顺序递增
ordered.add(new OrderedGatewayFilter(gatewayFilter, i 1));
}
}
return ordered;
}
4、参数解析
org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType#normalize
:
DEFAULT {
@Override
public Map<String, Object> normalize(Map<String, String> args,
ShortcutConfigurable shortcutConf, SpelExpressionParser parser,
BeanFactory beanFactory) {
Map<String, Object> map = new HashMap<>();
int entryIdx = 0;
for (Map.Entry<String, String> entry : args.entrySet()) {
//格式化key,解析出的“_genkey_0”、“_genkey_1”,根据GatewayFilter#shortcutFieldOrder配置的参数名称集合匹配
String key = normalizeKey(entry.getKey(), entryIdx, shortcutConf, args);
//解析value,支持Spring SpEL表达式:#{}
Object value = getValue(parser, beanFactory, entry.getValue());
map.put(key, value);
entryIdx ;
}
return map;
}
}
normalizeKey()
定义如下:
static String normalizeKey(String key, int entryIdx, ShortcutConfigurable argHints,
Map<String, String> args) {
// RoutePredicateFactory has name hints and this has a fake key name
// replace with the matching key hint
if (key.startsWith(NameUtils.GENERATED_NAME_PREFIX)
&& !argHints.shortcutFieldOrder().isEmpty() && entryIdx < args.size()
&& entryIdx < argHints.shortcutFieldOrder().size()) {
//调用ShortcutConfigurable#shortcutFieldOrder,获取参数名称集合,这个一般是在各自定义GatewayFilterFactory中实现
key = argHints.shortcutFieldOrder().get(entryIdx);
}
return key;
}
如StripPrefixGatewayFilterFactory#shortcutFieldOrder
定义如下:
public static final String PARTS_KEY = "parts";
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(PARTS_KEY);
}
5、自定义GateFilterFactory
总结
分析GatewayFilter
的加载过程,我们以StripPrefixGatewayFilterFactory
为例,介绍下在自定义GatewayFilterFactory时,主要注意以下几点:
构造方法中指定配置类类型的Class
public StripPrefixGatewayFilterFactory() {
super(Config.class);
}
shortcutFieldOrder()
方法指定参数名称,按照先后顺序和配置文件中参数进行匹配,同时名称和Configuration
中属性名称匹配,这样配置参数就初始化到Configuration
实例中
public static final String PARTS_KEY = "parts";
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(PARTS_KEY);
}
public static class Config {
private int parts;
}
GatewayFilterFactory
类的核心方法apply(Config config)
,输入初始化完成的Configuration
实例,一般通过匿名内部类方式构建一个GatewayFilter
进行返回,这个GatewayFilter
封装的就是我们需要实现的业务逻辑:
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
addOriginalRequestUrl(exchange, request.getURI());
String path = request.getURI().getRawPath();
String newPath = "/"
Arrays.stream(StringUtils.tokenizeToStringArray(path, "/"))
.skip(config.parts).collect(Collectors.joining("/"));
newPath = (newPath.length() > 1 && path.endsWith("/") ? "/" : "");
ServerHttpRequest newRequest = request.mutate().path(newPath).build();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR,
newRequest.getURI());
return chain.filter(exchange.mutate().request(newRequest).build());
}
@Override
public String toString() {
return filterToStringCreator(StripPrefixGatewayFilterFactory.this)
.append("parts", config.getParts()).toString();
}
};
}
总结
至此,Route
加载以及解析的整个流程分析完成,解析后的Route
集合数据会被缓存到CachingRouteLocator.routes
属性中,通过getRoutes()
可以获取到该数据。