Route 加载流程

栏目: IT技术 · 发布时间: 4年前

内容简介:网关服务核心功能是路由转发,即将接收的请求如何正确的路由到下层具体的服务模块。下面分析下这些路由信息构建的流程。上面就是包含有两条路由信息的配置文件,通过

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


以上所述就是小编给大家介绍的《Route 加载流程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据科学入门

数据科学入门

[美] Joel Grus / 高蓉、韩波 / 人民邮电出版社 / 2016-3 / 69.00元

数据科学是一个蓬勃发展、前途无限的行业,有人将数据科学家称为“21世纪头号性感职业”。本书从零开始讲解数据科学工作,教授数据科学工作所必需的黑客技能,并带领读者熟悉数据科学的核心知识——数学和统计学。 作者选择了功能强大、简单易学的Python语言环境,亲手搭建工具和实现算法,并精心挑选了注释良好、简洁易读的实现范例。书中涵盖的所有代码和数据都可以在GitHub上下载。 通过阅读本书,......一起来看看 《数据科学入门》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换