Route 加载流程
source link: https://mp.weixin.qq.com/s/p-GV4lNCqnbD-d9PW5_XZA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Route
加载
网关服务核心功能是路由转发,即将接收的请求如何正确的路由到下层具体的服务模块。下面分析下这些路由信息构建的流程。
路由信息配置文件:
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()
可以获取到该数据。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK