内容简介:网关服务核心功能是路由转发,即将接收的请求如何正确的路由到下层具体的服务模块。下面分析下这些路由信息构建的流程。上面就是包含有两条路由信息的配置文件,通过
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()
可以获取到该数据。
以上所述就是小编给大家介绍的《Route 加载流程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Route 加载流程
- Spring解密 - Bean 加载流程
- Android源码分析-setContentView加载布局流程
- Tinker源码分析(三):加载dex补丁流程
- Tinker源码分析(五):加载so补丁流程
- 深入 Java 类加载全流程,值得你收藏
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。