Spring Cloud Gateway(五):路由定位器 RouteLocator


本文基於 spring cloud gateway 2.0.1

1、簡介

直接 獲取 路 由 的 方法 是 通過 RouteLocator 接口 獲取。 同樣, 該 頂 級 接口 有多 個 實現 類,

RouteLocator 路由定位器,顧名思義就是用來獲取路由的方法。該路由定位器為頂級接口有多個實現類,如類圖所示,本節會對其實現類一一進行介紹。

路由定位器

通過類圖可知,路由定位器接口有三種實現方法:

  • RouteDefinitionRouteLocator 基於路由定義的定位器
  • CachingRouteLocator 基於緩存的路由定位器
  • CompositeRouteLocator 基於組合方式的路由定位器

2、RouteLocator 路由定位器

與上節學習的路由定義定位器接口類似,RouteLocator 路由定位器只有一個 getRoutes 方法,用來獲取路由信息。

public interface RouteLocator {
    //獲取路由對象
	Flux<Route> getRoutes();
}

2.1、Route 路由對象

Route 路由定義了路由斷言、過濾器、路由地址及路由優先級等信息。當請求到達時,在轉發到代理服務之前,會依次經過路由斷言匹配路由 和 網關過濾器處理。

public class Route implements Ordered {

    //路由 Id 
    private final String id; 
    //路由地址 
    private final URI uri; 
    //路由的優先級 
    private final int order; 
    //路由斷言,判斷請求路徑是否匹配
	private final AsyncPredicate<ServerWebExchange> predicate;
    //網關過濾器
	private final List<GatewayFilter> gatewayFilters;
    
    -----------------------省略-------------------------
}

3、 RouteDefinitionRouteLocator 基於路由定義的定位器

3.1、初始化

RouteDefinitionRouteLocator 構造函數有多個參數:路由定義定位器、路由斷言工廠、網關過濾器及網關配置對象。 根據傳入的參數,設置 routeDefinitionLocator 和 網關配置,並初始化路由斷言 和 網關過濾器。 RouteDefinitionRouteLocator 的實現方式是基於路由定義來獲取路由,它實現了 RouteLocator 接口,用來獲取路由信息。

public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAware, ApplicationEventPublisherAware {
	protected final Log logger = LogFactory.getLog(getClass());

	private final RouteDefinitionLocator routeDefinitionLocator;
	private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>();
	private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>();
	private final GatewayProperties gatewayProperties;
	private final SpelExpressionParser parser = new SpelExpressionParser();
	private BeanFactory beanFactory;
	private ApplicationEventPublisher publisher;

	public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,
									   List<RoutePredicateFactory> predicates,
									   List<GatewayFilterFactory> gatewayFilterFactories,
									   GatewayProperties gatewayProperties) {
		//設置路由定義定位器
		this.routeDefinitionLocator = routeDefinitionLocator;
		//初始化路由斷言工廠
		initFactories(predicates);
		//初始化網關過濾器
		gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));
		this.gatewayProperties = gatewayProperties;
	}

	@Autowired
	private Validator validator;

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}

	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
		this.publisher = publisher;
	}

	private void initFactories(List<RoutePredicateFactory> predicates) {
		predicates.forEach(factory -> {
			String key = factory.name();
			if (this.predicates.containsKey(key)) {
				this.logger.warn("A RoutePredicateFactory named "+ key
						+ " already exists, class: " + this.predicates.get(key)
						+ ". It will be overwritten.");
			}
			this.predicates.put(key, factory);
			if (logger.isInfoEnabled()) {
				logger.info("Loaded RoutePredicateFactory [" + key + "]");
			}
		});
	}
    --------------------------------省略---------------------------------
}
  • 此種方式的路由獲取是通過 RouteDefinitionRouteLocator 獲取 RouteDefinition 並將路由定義轉換成路由對象
  • 這里的routeDefinitionLocator是CompositeRouteDefinitionLocator,它組合了InMemoryRouteDefinitionRepository、PropertiesRouteDefinitionLocator、DiscoveryClientRouteDefinitionLocator三個RouteDefinitionLocator。
  • PropertiesRouteDefinitionLocator是直接使用GatewayProperties的getRoutes()獲取,其是通過spring.cloud.gateway.routes配置得來。

3.2、RouteDefinition 轉換成 Route 的流程

![image](路由轉換流程圖

// RouteDefinitionRouteLocator.java

@Override
	public Flux<Route> getRoutes() {
		return this.routeDefinitionLocator.getRouteDefinitions()
				.map(this::convertToRoute)
				//TODO: error handling
				.map(route -> {
					if (logger.isDebugEnabled()) {
						logger.debug("RouteDefinition matched: " + route.getId());
					}
					return route;
				});


		/* TODO: trace logging
			if (logger.isTraceEnabled()) {
				logger.trace("RouteDefinition did not match: " + routeDefinition.getId());
			}*/
	}

	private Route convertToRoute(RouteDefinition routeDefinition) {
		AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
		List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);

		return Route.async(routeDefinition)
				.asyncPredicate(predicate)
				.replaceFilters(gatewayFilters)
				.build();
	}
  • getRoutes() :根據傳入的 RouteDefinitionLocator 獲取路由定義對象,使用map方法將每個 RouteDefinition 轉換為 Route。

  • RouteDefinitionLocator#convertToRoute :是具體的轉換方法,轉換過程中涉及到路由斷言 和 網關過濾器的處理,最后構建為Route 對象。

  • 此處網關過濾器處理包括兩種,一種是默認過濾器,作用於所有路由;一種是指定路由的自定義過濾器。首先獲取默認過濾器,根據過濾器名稱獲取對應的過濾器,最終轉換成有優先級的OrderedGatewayFilter。

3.2.1、convertToRoute##combinePredicates

combinePredicates主要是對找出來的predicate進行and操作

private AsyncPredicate<ServerWebExchange> combinePredicates(RouteDefinition routeDefinition) {
		List<PredicateDefinition> predicates = routeDefinition.getPredicates();
		AsyncPredicate<ServerWebExchange> predicate = lookup(routeDefinition, predicates.get(0));

		for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) {
			AsyncPredicate<ServerWebExchange> found = lookup(routeDefinition, andPredicate);
			predicate = predicate.and(found);
		}

		return predicate;
	}

	@SuppressWarnings("unchecked")
	private AsyncPredicate<ServerWebExchange> lookup(RouteDefinition route, PredicateDefinition predicate) {
		RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());
		if (factory == null) {
            throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName());
		}
		Map<String, String> args = predicate.getArgs();
		if (logger.isDebugEnabled()) {
			logger.debug("RouteDefinition " + route.getId() + " applying "
					+ args + " to " + predicate.getName());
		}

        Map<String, Object> properties = factory.shortcutType().normalize(args, factory, this.parser, this.beanFactory);
        Object config = factory.newConfig();
        ConfigurationUtils.bind(config, properties,
                factory.shortcutFieldPrefix(), predicate.getName(), validator);
        if (this.publisher != null) {
            this.publisher.publishEvent(new PredicateArgsEvent(this, route.getId(), properties));
        }
        return factory.applyAsync(config);
	}

3.2.2、convertToRoute##getFilters

getFilters 主要是利用loadGatewayFilters獲取filter,使用AnnotationAwareOrderComparator進行排序
loadGatewayFilters利用工廠方法,使用GatewayFilterFactory根據config 獲取具體的GatewayFilter實例

@SuppressWarnings("unchecked")
	private List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) {
		List<GatewayFilter> filters = filterDefinitions.stream()
				.map(definition -> {
					GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());
					if (factory == null) {
                        throw new IllegalArgumentException("Unable to find GatewayFilterFactory with name " + definition.getName());
					}
					Map<String, String> args = definition.getArgs();
					if (logger.isDebugEnabled()) {
						logger.debug("RouteDefinition " + id + " applying filter " + args + " to " + definition.getName());
					}

                    Map<String, Object> properties = factory.shortcutType().normalize(args, factory, this.parser, this.beanFactory);

                    Object configuration = factory.newConfig();

                    ConfigurationUtils.bind(configuration, properties,
                            factory.shortcutFieldPrefix(), definition.getName(), validator);

                    GatewayFilter gatewayFilter = factory.apply(configuration);
                    if (this.publisher != null) {
                        this.publisher.publishEvent(new FilterArgsEvent(this, id, properties));
                    }
                    return gatewayFilter;
				})
				.collect(Collectors.toList());

		ArrayList<GatewayFilter> ordered = new ArrayList<>(filters.size());
		for (int i = 0; i < filters.size(); i++) {
			GatewayFilter gatewayFilter = filters.get(i);
			if (gatewayFilter instanceof Ordered) {
				ordered.add(gatewayFilter);
			}
			else {
				ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));
			}
		}

		return ordered;
	}

	private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) {
		List<GatewayFilter> filters = new ArrayList<>();

		//TODO: support option to apply defaults after route specific filters?
		if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {
			filters.addAll(loadGatewayFilters("defaultFilters",
					this.gatewayProperties.getDefaultFilters()));
		}

		if (!routeDefinition.getFilters().isEmpty()) {
			filters.addAll(loadGatewayFilters(routeDefinition.getId(), routeDefinition.getFilters()));
		}

		AnnotationAwareOrderComparator.sort(filters);
		return filters;
	}

4、 CachingRouteLocator 基於緩存的路由定位器

public class CachingRouteLocator implements RouteLocator {

	private final RouteLocator delegate;
	private final Flux<Route> routes;
	private final Map<String, List> cache = new HashMap<>();

	public CachingRouteLocator(RouteLocator delegate) {
		this.delegate = delegate;
		routes = CacheFlux.lookup(cache, "routes", Route.class)
				.onCacheMissResume(() -> this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE));
	}

	@Override
	public Flux<Route> getRoutes() {
		return this.routes;
	}

	/**
	 * Clears the routes cache
	 * @return routes flux
	 */
	public Flux<Route> refresh() {
		this.cache.clear();
		return this.routes;
	}

	@EventListener(RefreshRoutesEvent.class)
	/* for testing */ void handleRefresh() {
		refresh();
	}
}

基於緩存的路由定位器比較簡單和緩存路由定義定位器比較類似,只需要調用 RouteLocator# getRoutes 即可獲取路由。

根據傳入的路由定位器獲取路由信息並存儲到緩存中。通過監聽 RefreshRoutesEvent 事件刷新緩存的路由信息。

5、 CompositeRouteLocator 基於組合方式的路由定位器

public class CompositeRouteLocator implements RouteLocator {

	private final Flux<RouteLocator> delegates;

	public CompositeRouteLocator(Flux<RouteLocator> delegates) {
		this.delegates = delegates;
	}

	@Override
	public Flux<Route> getRoutes() {
		return this.delegates.flatMap(RouteLocator::getRoutes);
	}
}

組合方式的路由定位器,將實現 RouteLocator 接口的路由定位器組合在一起,提供獲取路由的統一入口。

6、小結

RouteLocator 接口用於獲取路由信息,其有三個實現類

  • RouteDefinitionRouteLocator
  • CompositeRouteLocator
  • CachingRouteLocator

最終使用的是CachingRouteLocator,它包裝了CompositeRouteLocator,而CompositeRouteLocator則組合了RouteDefinitionRouteLocator。

RouteDefinitionRouteLocator 與 RouteDefinitionLocator比較容易混淆,前者是一個RouteLocator(路由定位器),后者是一個RouteDefinitionLocator(路由定義定位器),前者的 RouteDefinitionRouteLocator 主要從后者獲取路由定義信息。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM