spring cloud gateway 原始碼解析(2)動態路由

彈肖邦の夜曲發表於2019-01-19

先看看gateway是怎麼獲取我們配置的路由:
在gateway啟動時,GatewayAutoConfiguration幫我們註冊了一系列beans

    @Bean
    @Primary
    public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {
        //這裡注入所有RouteDefinitionLocator,我們在配置檔案裡配置的路由就是通過子類PropertiesRouteDefinitionLocator來完成的,
        //可以參考InMemoryRouteDefinitionRepository實現自己的RouteDefinition的儲存,RouteDefinition會在後面轉換成Route
        return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
    }

    //接著看CompositeRouteDefinitionLocator類裡的獲取路由定義方法:
    public Flux<RouteDefinition> getRouteDefinitions() {
        //即輪訓所有RouteDefinitionLocator的市現率呼叫getRouteDefinitions,這樣就把所有RouteDefinition整合到一起了
        return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions);
    }
    
    //接著看GatewayAutoConfiguration,剛才的bean RouteDefinitionLocator 作為引數注入到routeDefinitionLocator
    @Bean
    public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
                                                   List<GatewayFilterFactory> GatewayFilters,
                                                   List<RoutePredicateFactory> predicates,
                                                   RouteDefinitionLocator routeDefinitionLocator) {
        return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters, properties);
    }

 //看RouteDefinitionRouteLocator類:
public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAware, ApplicationEventPublisherAware {
    ······

    public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,
                                       List<RoutePredicateFactory> predicates,
                                       List<GatewayFilterFactory> gatewayFilterFactories,
                                       GatewayProperties gatewayProperties) {
        this.routeDefinitionLocator = routeDefinitionLocator;
        //RoutePredicateFactory放到map裡,去掉RoutePredicateFactory字尾,所以配置時就可以寫成
        //predicates:
        //    - Path=/abc/*
        //的形式而不用寫完整的PathRoutePredicateFactory    
        initFactories(predicates);
        //把bean名字裡的GatewayFilterFactory去掉,所以我們配置檔案裡配置時也要把GatewayFilterFactory去掉,不然會找不到
        gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));
        this.gatewayProperties = gatewayProperties;
    }
    ······ 

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

    private Route convertToRoute(RouteDefinition routeDefinition) {
        //獲取所有匹配規則,所有的規則都滿足才走對應Route
        AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
        //獲取全部過濾器
        List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);

        return Route.async(routeDefinition)
                .asyncPredicate(predicate)
                .replaceFilters(gatewayFilters)
                .build();
    }
 

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

        //把全域性過濾器新增到當前路由,loadGatewayFilters呼叫GatewayFilterFactory裡的apply配置類裡面的靜態Config類,並且把沒實現Ordered介面的類用OrderedGatewayFilter代理一下,方便下面排序
        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;
    }

    private AsyncPredicate<ServerWebExchange> combinePredicates(RouteDefinition routeDefinition) { 
        //獲取路由的判斷條件,比如我們大多是根據url判斷,用的是PathRoutePredicateFactory
        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);//如果有多個匹配規則就要滿足所有的才可以
        }
        //返回的結果會在RoutePredicateHandlerMapping類裡使用apply方法呼叫 
        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);
        //獲取RoutePredicateFactory實現類裡的靜態Config
        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));
        }
        //這裡實際呼叫的是ServerWebExchangeUtils類裡的toAsyncPredicate方法,並配置的config
        return factory.applyAsync(config);
    }
}


  // 把剛才的bean RouteDefinitionRouteLocator作為引數注入到routeLocators
    @Bean
    @Primary 
    public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
        //這裡CompositeRouteLocator與上面的CompositeRouteDefinitionLocator類似,也是遍歷routeLocators呼叫getRoutes方法整合所有Route
        return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
    }
    //接著看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)
                 //每次請求都會獲取routes,每當拿快取又找不到的時候(即我們修改了路由),就會呼叫上面CompositeRouteLocator的getRoutes方法重新獲取路由
                .onCacheMissResume(() -> this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE));
    }

    ·······

    public Flux<Route> refresh() {
        this.cache.clear();//清掉路由快取
        return this.routes;
    }

    @EventListener(RefreshRoutesEvent.class)
    /* for testing */ void handleRefresh() {//更新路由用的是spring事件機制
        refresh();
    }
}

 //接著看GatewayAutoConfiguration,剛才的bean CachingRouteLocator作為引數注入到RouteLocator
    @Bean
    public RoutePredicateHandlerMapping routePredicateHandlerMapping(
            FilteringWebHandler webHandler, RouteLocator routeLocator,
            GlobalCorsProperties globalCorsProperties, Environment environment) {
        return new RoutePredicateHandlerMapping(webHandler, routeLocator,
                globalCorsProperties, environment);
    }

//接著看RoutePredicateHandlerMapping

public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {

    private final FilteringWebHandler webHandler;
    private final RouteLocator routeLocator;
    private final Integer managmentPort;

    public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
        this.webHandler = webHandler;
        this.routeLocator = routeLocator;

        if (environment.containsProperty("management.server.port")) {
            managmentPort = new Integer(environment.getProperty("management.server.port"));
        } else {
            managmentPort = null;
        }
        //這裡設定了順序,使得閘道器路由的匹配在requestmapping和handlefunction之後
        setOrder(1);        
        setCorsConfigurations(globalCorsProperties.getCorsConfigurations());
    }

    @Override//方法在原始碼解析(1)中說的AbstractHandlerMapping類中獲取Handler時呼叫
    protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
        // don`t handle requests on the management port if set
        if (managmentPort != null && exchange.getRequest().getURI().getPort() == managmentPort.intValue()) {
            //不處理特定配置埠的路由,返回empty即不用閘道器路由的handler,AbstractHandlerMapping繼續找下一個
            return Mono.empty();
        }
        exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());

        return lookupRoute(exchange)//查詢路由
                // .log("route-predicate-handler-mapping", Level.FINER) //name this
                .flatMap((Function<Route, Mono<?>>) r -> {
                    exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r);
                    }
                    //把Route放到exchange的屬性了,後面有幾個過濾器用到了
                    exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
                    //返回構造該類時傳入的FilteringWebHandler,之後的流程就會跟上一篇裡說的走下去了
                    return Mono.just(webHandler);
                }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
                   //找不到匹配的路由的話進到這裡,之後基本就是404了
                    exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
                    if (logger.isTraceEnabled()) {
                        logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
                    }
                })));
    }

     ······

    protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
       //呼叫上面提到的CachingRouteLocator的getRoutes方法獲取所有Routes
        return this.routeLocator
                .getRoutes()
                //individually filter routes so that filterWhen error delaying is not a problem
                .concatMap(route -> Mono
                        .just(route)
                        .filterWhen(r -> {//過濾出符合的路由
                            // add the current route we are testing
                            exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
                            //呼叫上面說的ServerWebExchangeUtils類裡的toAsyncPredicate方法返回的lambda 
                            //裡面會呼叫Predicate的實現類(都是lambda)的test方法返回true或者false
                            return r.getPredicate().apply(exchange);
                        })
                        //instead of immediately stopping main flux due to error, log and swallow it
                        .doOnError(e -> logger.error("Error applying predicate for route: "+route.getId(), e))
                        .onErrorResume(e -> Mono.empty())
                )
                // .defaultIfEmpty() put a static Route not found
                // or .switchIfEmpty()
                // .switchIfEmpty(Mono.<Route>empty().log("noroute"))
                .next()//過濾出一個後就停止了
                //TODO: error handling
                .map(route -> {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Route matched: " + route.getId());
                    }
                    validateRoute(route, exchange);
                    return route;
                });
 
    }

   ·····
}

說了那麼多終於把閘道器匹配路由的流程說完了,如果上面都看明白的話,動態路由就好辦了
閘道器已經把InMemoryRouteDefinitionRepository註冊成bean(也可以參考這個類自己實現RouteDefinitionRepository介面),我們把它當作個service注入到controller,
前端把RouteDefinition用json的格式post過來,我們呼叫InMemoryRouteDefinitionRepository的save或者delete方法,再用spring的事件觸發RefreshRoutesEvent事件來重新整理路由就行了,等下次請求的時候就可以拿到新的路由配置了
順序是:
1.RoutePredicateHandlerMapping 的lookupRoute方法,由於路由重新整理事件把路由快取清了,所以重新獲取
2.CompositeRouteLocator 的getRoutes方法遍歷所有RouteLocator實現類的getRoutes方法
3.RouteDefinitionRouteLocator 的getRoutes方法裡重新獲取了所有的路由定義,也就把我們剛剛用事件新增的路由也獲取了

相關文章