前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Route加载流程

Route加载流程

作者头像
Reactor2020
发布2023-03-22 18:52:58
7450
发布2023-03-22 18:52:58
举报

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

代码语言:javascript
复制
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结构。

代码语言:javascript
复制
@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代码见下:

代码语言:javascript
复制
/**
 * 路由定义实体信息,包含路由的定义信息
 * @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事件:

代码语言:javascript
复制
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));
}

TipsContextRefreshedEvent是在ApplicationContext.refresh()执行完成后触发,即Context初始化全部完成。

WeightCalculatorWebFilter类监听到RefreshRoutesEvent事件,触发调用CachingRouteLocator#getRoutes()获取Route集合:

代码语言:javascript
复制
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

代码语言:javascript
复制
public Flux<Route> getRoutes() {
	return this.routes;
}

3、routes初始化流程

routes在构造方法中进行初始化:

代码语言:javascript
复制
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 -> CompositeRouteLocatorCompositeRouteLocator,汇聚了所有的RouteLocator集合,主要包含两类:一类是RouteDefinitionRouteLocator,基于RouteDefinition获取Route;另一类是编程方式自定义创建RouteLocator,如:

代码语言:javascript
复制
@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();
}

这里我们主要分析RouteDefinitionRoute流程,所以需要关注RouteDefinitionRouteLocator#getRoutes

代码语言:javascript
复制
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类型实例,将InMemoryRouteDefinitionRepositoryPropertiesRouteDefinitionLocator包装混合到一起。

代码语言:javascript
复制
public Flux<RouteDefinition> getRouteDefinitions() {
	return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions);
}

Tips:这里涉及的各种代理关系后续分析自动装配类GatewayAutoConfiguration再来细说。

routeDefinitionLocator.getRouteDefinitions()实际上调用InMemoryRouteDefinitionRepositoryPropertiesRouteDefinitionLocatorgetRouteDefinitions方法获取到RouteDefinition集合,然后执行convertToRoute()方法将RouteDefinition转成Route对象。

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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()定义如下:

代码语言:javascript
复制
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定义如下:

代码语言:javascript
复制
public static final String PARTS_KEY = "parts";

@Override
public List<String> shortcutFieldOrder() {
	return Arrays.asList(PARTS_KEY);
}

5、自定义GateFilterFactory总结

分析GatewayFilter的加载过程,我们以StripPrefixGatewayFilterFactory为例,介绍下在自定义GatewayFilterFactory时,主要注意以下几点:

构造方法中指定配置类类型的Class

代码语言:javascript
复制
public StripPrefixGatewayFilterFactory() {
	super(Config.class);
}

shortcutFieldOrder()方法指定参数名称,按照先后顺序和配置文件中参数进行匹配,同时名称和Configuration中属性名称匹配,这样配置参数就初始化到Configuration实例中

代码语言:javascript
复制
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封装的就是我们需要实现的业务逻辑:

代码语言:javascript
复制
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()可以获取到该数据。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Reactor2020 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Route加载
  • 流程分析
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档