Route加载流程

2023-03-22 18:52:58 浏览数 (2)

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()可以获取到该数据。

0 人点赞