Spring Cloud Gateway(四):路由定義定位器 RouteDefinitionLocator


本文基於 spring cloud gateway 2.0.1

1、簡介

RouteDefinitionLocator 是路由定義定位器的頂級接口,它的主要作用就是讀取路由的配置信息(org.springframework.cloud.gateway.route.RouteDefinition)。它有五種不同的實現類,如圖

路由定義定位器

2、RouteDefinitionLocator

org.springframework.cloud.gateway.route.RouteDefinitionLocator ,路由定義定位器接口,只有一個方法,用來獲取路由定義列表的方法。

public interface RouteDefinitionLocator {

	Flux<RouteDefinition> getRouteDefinitions();
}

通過 RouteDefinitionLocator 的類圖,可以看出該接口有多個實現類:

  • PropertiesRouteDefinitionLocator:基於屬性配置
  • DiscoveryClientRouteDefinitionLocator:基於服務發現
  • CompositeRouteDefinitionLocator:組合方式
  • CachingRouteDefinitionLocator:緩存方式
  • 其中還有一個接口 RouteDefinitionRepository 繼承自RouteDefinitionLocator,用於對路由定義的操作(保存、刪除路由定義)

2.1、RouteDefinition

RouteDefinition 作為GatewayProperties中的屬性,在網關啟動的時候讀取配置文件中的相關配置信息

@Validated
public class RouteDefinition {
    
    @NotEmpty
    private String id = UUID.randomUUID().toString();
    
   
    @NotEmpty
    @Valid
    private List<PredicateDefinition> predicates = new ArrayList();
    
   
    @Valid
    private List<FilterDefinition> filters = new ArrayList();
    
 
    @NotNull
    private URI uri;
    
    
    private int order = 0;

    public RouteDefinition() {
    }

    public RouteDefinition(String text) {
        int eqIdx = text.indexOf(61);
        if (eqIdx <= 0) {
            throw new ValidationException("Unable to parse RouteDefinition text '" + text + "', must be of the form name=value");
        } else {
            this.setId(text.substring(0, eqIdx));
            String[] args = StringUtils.tokenizeToStringArray(text.substring(eqIdx + 1), ",");
            this.setUri(URI.create(args[0]));

            for(int i = 1; i < args.length; ++i) {
                this.predicates.add(new PredicateDefinition(args[i]));
            }

        }
    }
   ----------------------省略----------------------------
}

在 RouteDefinition 中,主要有五個屬性:

id:路由id,默認為uuid

predicates:PredicateDefinition 路由斷言定義列表

filters:FilterDefinition 過濾器定義列表

uri:URI 轉發地址

order:優先級

進入斷言和路由器屬性可以看到他們是一個 Map 數據結構,可以存放多個對應的 鍵值對數組

3、RouteDefinitionRepository & InMemoryRouteDefinitionRepository

RouteDefinitionRepository 接口中的方法用來對RouteDefinition進行增、刪、查操作

public interface RouteDefinitionRepository extends RouteDefinitionLocator, RouteDefinitionWriter {
}
//讀取路由定義信息
public interface RouteDefinitionLocator {

	Flux<RouteDefinition> getRouteDefinitions();
}
//對路由定會進行保存和刪除操作
public interface RouteDefinitionWriter {
    Mono<Void> save(Mono<RouteDefinition> route);

    Mono<Void> delete(Mono<String> routeId);
}

RouteDefinitionRepository 通過繼承自 RouteDefinitionLocator、 RouteDefinitionWriter,封裝了對路由定義信息的獲取、增加、刪除操作,在網關內置API端點接口時會用到這些操作。

InMemoryRouteDefinitionRepository 實現了 RouteDefinitionRepository 接口,基於內存的路由定義倉庫,同時也是唯一提供的實現類。我們可以根據需要自定義擴展,存放到其它的存儲介質中。

public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
    private final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap());

    public InMemoryRouteDefinitionRepository() {
    }

    //保存路由定義到內存中
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap((r) -> {
            this.routes.put(r.getId(), r);
            return Mono.empty();
        });
    }
    //根據路由id從內存中刪除指定路由定義
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap((id) -> {
            if (this.routes.containsKey(id)) {
                this.routes.remove(id);
                return Mono.empty();
            } else {
                return Mono.defer(() -> {
                    return Mono.error(new NotFoundException("RouteDefinition not found: " + routeId));
                });
            }
        });
    }

    //獲取內存中路由定義列表
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(this.routes.values());
    }
}

4、PropertiesRouteDefinitionLocator 基於配置屬性的路由定義定位器

從配置文件 yaml或properties中讀取路由配置信息,如代碼所示

public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {
    private final GatewayProperties properties;

    public PropertiesRouteDefinitionLocator(GatewayProperties properties) {
        this.properties = properties;
    }

    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(this.properties.getRoutes());
    }
}

PropertiesRouteDefinitionLocator 通過構造函數傳入 GatewayProperties 對象,然后從該對象中讀取路由配置信息

5、DiscoveryClientRouteDefinitionLocator 基於服務發現的路由定義定位器

該類通過服務發現組件從注冊中心獲取服務信息,此時路由定義的源就是配置中心

public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
    //服務發現客戶端
    private final DiscoveryClient discoveryClient;
    //服務發現屬性
    private final DiscoveryLocatorProperties properties;
    //路由id前綴
    private final String routeIdPrefix;

   ------------------------------省略--------------------------------
}
//服務發現屬性對象
@ConfigurationProperties("spring.cloud.gateway.discovery.locator")
public class DiscoveryLocatorProperties {
   // 開啟服務發現
   private boolean enabled = false; 
   // 路由前綴,默認為 discoveryClient. getClass(). getSimpleName() + "_". 
   private String routeIdPrefix; 
   // SpEL 表達式,判斷網關是否集成一個服務,默認為 true 
   private String includeExpression = "true"; 
   // SpEL 表達式,為每個路由創建uri,默認為'lb://'+ serviceId 
   private String urlExpression = "'lb://'+ serviceId"; 
   // 在 斷言 和 過濾器 中使用小寫 serviceId,默認為 false
   private boolean lowerCaseServiceId = false;
    //路由斷言定義列表
    private List<PredicateDefinition> predicates = new ArrayList();
    //過濾器定義列表
    private List<FilterDefinition> filters = new ArrayList();
  ------------------------------省略--------------------------------
}

在 DiscoveryLocatorProperties 定義了以上屬性,要啟用基於服務發現的路由定義定位器就必須設置

spring.cloud.gateway.discovery.locator.enabled= true

includeExpression 屬性判斷網關是否集成一個服務,默認為true,
根據 includeExpression 表達式,過濾不符合的 ServiceInstance。

DiscoveryClientRouteDefinitionLocator -> getRouteDefinitions()

@Override
	public Flux<RouteDefinition> getRouteDefinitions() {
        //對 includeExpression 和 urlExpression 的 表達式 處理
		SpelExpressionParser parser = new SpelExpressionParser();
		Expression includeExpr = parser.parseExpression(properties.getIncludeExpression());
		Expression urlExpr = parser.parseExpression(properties.getUrlExpression());

		Predicate<ServiceInstance> includePredicate;
		if (properties.getIncludeExpression() == null || "true".equalsIgnoreCase(properties.getIncludeExpression())) {
			includePredicate = instance -> true;
		} else {
			includePredicate = instance -> {
				Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class);
				if (include == null) {
					return false;
				}
				return include;
			};
		}
        //通過注冊中心查找服務組裝路由定義信息
		return Flux.fromIterable(discoveryClient.getServices())
				.map(discoveryClient::getInstances)
				.filter(instances -> !instances.isEmpty())
				.map(instances -> instances.get(0))
				// 根據 includeExpression 表達式,過濾不符合的 ServiceInstance
				.filter(includePredicate)
				.map(instance -> {
					String serviceId = instance.getServiceId();

                    RouteDefinition routeDefinition = new RouteDefinition();
                    routeDefinition.setId(this.routeIdPrefix + serviceId);
					String uri = urlExpr.getValue(evalCtxt, instance, String.class);
					routeDefinition.setUri(URI.create(uri));

					final ServiceInstance instanceForEval = new DelegatingServiceInstance(instance, properties);
                    
                    //添加配置的斷言表達式
					for (PredicateDefinition original : this.properties.getPredicates()) {
						PredicateDefinition predicate = new PredicateDefinition();
						predicate.setName(original.getName());
						for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
							String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
							predicate.addArg(entry.getKey(), value);
						}
						routeDefinition.getPredicates().add(predicate);
					}
                    
                    //添加配置的過濾器
                    for (FilterDefinition original : this.properties.getFilters()) {
                    	FilterDefinition filter = new FilterDefinition();
                    	filter.setName(original.getName());
						for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
							String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
							filter.addArg(entry.getKey(), value);
						}
						routeDefinition.getFilters().add(filter);
					}

                    return routeDefinition;
				});
	}

	String getValueFromExpr(SimpleEvaluationContext evalCtxt, SpelExpressionParser parser, ServiceInstance instance, Map.Entry<String, String> entry) {
		Expression valueExpr = parser.parseExpression(entry.getValue());
		return valueExpr.getValue(evalCtxt, instance, String.class);
	}

	private static class DelegatingServiceInstance implements ServiceInstance {

		final ServiceInstance delegate;
		private final DiscoveryLocatorProperties properties;

		private DelegatingServiceInstance(ServiceInstance delegate, DiscoveryLocatorProperties properties) {
			this.delegate = delegate;
			this.properties = properties;
		}

		@Override
		public String getServiceId() {
			if (properties.isLowerCaseServiceId()) {
				return delegate.getServiceId().toLowerCase();
			}
			return delegate.getServiceId();
		}

	
	}

從源碼可以看出,getRouteDefinitions 方法通過服務發現客戶端從注冊中心獲取服務信息,組裝成RouteDefinition路由定義列表,並將配置中的路由斷言和過濾應用到RouteDefinition 中

6、CachingRouteDefinitionLocator 基於緩存的路由定義定位器

緩存方式的路由定義定位器,通過傳入路由定義定位器獲取路由定義並緩存到本地。通過監聽路由刷新時間RefreshRoutesEvent 來刷新本地緩存的路由定義信息

public class CachingRouteDefinitionLocator implements RouteDefinitionLocator {
    //路由定義定位器
    private final RouteDefinitionLocator delegate;
    //路由定義信息
    private final Flux<RouteDefinition> routeDefinitions;
    //本地緩存集合
    private final Map<String, List> cache = new HashMap();

    public CachingRouteDefinitionLocator(RouteDefinitionLocator delegate) {
        this.delegate = delegate;
        this.routeDefinitions = CacheFlux.lookup(this.cache, "routeDefs", RouteDefinition.class).onCacheMissResume(() -> {
            return this.delegate.getRouteDefinitions();
        });
    }

    public Flux<RouteDefinition> getRouteDefinitions() {
        return this.routeDefinitions;
    }

    //刷新本地緩存,先清空本地緩存再獲取一份新的路由定義信息存儲
    public Flux<RouteDefinition> refresh() {
        this.cache.clear();
        return this.routeDefinitions;
    }
   //監聽路由刷新事件,刷新本地緩存的路由定義信息
    @EventListener({RefreshRoutesEvent.class})
    void handleRefresh() {
        this.refresh();
    }
}

7、CompositeRouteDefinitionLocator 組合路由定義定位器

組合方式路由定義定位器使用組合模式進行實現,組合多個 RouteDefinitionLocator 的實現,為獲取路由定義信息 getRouteDefinitions 提供統一入口,組合的邏輯很簡單,通過傳入的路由定義定位器作為代理,具體的路由定義實際上是由傳入的路由定義定位器產生。

public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator {
    private final Flux<RouteDefinitionLocator> delegates;

    public CompositeRouteDefinitionLocator(Flux<RouteDefinitionLocator> delegates) {
        this.delegates = delegates;
    }

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


免責聲明!

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



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