21

Route 加载流程

 4 years ago
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));
}

TipsContextRefreshedEvent 是在 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 -> CompositeRouteLocatorCompositeRouteLocator ,汇聚了所有的 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();
}

这里我们主要分析 RouteDefinitionRoute 流程,所以需要关注 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 类型实例,将 InMemoryRouteDefinitionRepositoryPropertiesRouteDefinitionLocator 包装混合到一起。

public Flux<RouteDefinition> getRouteDefinitions() {
	return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions);
}

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

routeDefinitionLocator.getRouteDefinitions() 实际上调用 InMemoryRouteDefinitionRepositoryPropertiesRouteDefinitionLocatorgetRouteDefinitions 方法获取到 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() 可以获取到该数据。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK