Gateway 網關 之 自定義路由加載(源碼分析)


源碼梳理:

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


RouteDefinitionLocator

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

public interface RouteDefinitionLocator {
 
   Flux<RouteDefinition> getRouteDefinitions();
}

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

1、PropertiesRouteDefinitionLocator:基於屬性配置

2、DiscoveryClientRouteDefinitionLocator:基於服務發現

3、CompositeRouteDefinitionLocator:組合方式

4、CachingRouteDefinitionLocator:緩存方式

5、其中還有一個接口 RouteDefinitionRepository 繼承自RouteDefinitionLocator,用於對路由定義的操作(保存、刪除路由定義)

 


RouteDefinitionRepository & InMemoryRouteDefinitionRepository

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

  • RouteDefinitionLocator 定義了用來獲取路由定義列表的方法:
public interface RouteDefinitionLocator {
 
   Flux<RouteDefinition> getRouteDefinitions();
}
  • RouteDefinitionWriter 定義了 寫和刪除的方法
public interface RouteDefinitionWriter {
 
   Mono<Void> save(Mono<RouteDefinition> route);
 
   Mono<Void> delete(Mono<String> routeId);
} 

 

RouteDefinitionRepository 接口負責將兩個以上兩個接口方法匯集

public interface RouteDefinitionRepository extends RouteDefinitionLocator, RouteDefinitionWriter {
}

 

InMemoryRouteDefinitionRepository 實現了 RouteDefinitionRepository 接口,基於內存的路由定義倉庫,將 路由信息存儲再 內存中的 Map,提供服務的:讀、寫、刪除

public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
 
    /**
     * 用來存儲路由信息的 Map
     */
    private final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap());
 
    /**
     * 保存路由定義到內存中
     *
     * @param route
     * @return
     */
    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap((r) -> {
            this.routes.put(r.getId(), r);
            return Mono.empty();
        });
    }
 
    /**
     * 根據路由id從內存中刪除指定路由定義
     *
     * @param routeId
     * @return
     */
    @Override
    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));
                });
            }
        });
    }
 
    /**
     * 獲取內存中路由定義列表
     *
     * @return
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(this.routes.values());
    }
}

 


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

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

public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {
 
   private final GatewayProperties properties;
 
   public PropertiesRouteDefinitionLocator(GatewayProperties properties) {
      this.properties = properties;
   }
 
   @Override
   public Flux<RouteDefinition> getRouteDefinitions() {
      return Flux.fromIterable(this.properties.getRoutes());
   }
}

 


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

 


RouteDefinition (定義描述 Gateway 路由屬性的類)

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

@Validated
public class RouteDefinition {
 
    /**
     * 路由唯一ID ,默認為 UUID
     */
    private String id;
 
    /**
     * 路由斷言定義列表
     */
    @NotEmpty
    @Valid
    private List<PredicateDefinition> predicates = new ArrayList();
    /**
     * 過濾器定義列表
     */
    @Valid
    private List<FilterDefinition> filters = new ArrayList();
    /**
     * 轉發地址
     */
    @NotNull
    private URI uri;
 
    /**
     * 
     */
    private Map<String, Object> metadata = new HashMap();
 
    /**
     * 優先級
     */
    private int order = 0;
 
    public RouteDefinition() {
    }
 
    public RouteDefinition(String text) {
        int eqIdx = text.indexOf("=");
        if (eqIdx <= 0) {
            throw new ValidationException("Unable to parse RouteDefinition text '" + text + "'" +
                    ", must be of the form name=value");
        }
 
        setId(text.substring(0, eqIdx));
 
        String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ",");
 
        setUri(URI.create(args[0]));
 
        for (int i = 1; i < args.length; i++) {
            this.predicates.add(new PredicateDefinition(args[i]));
        }
    }
}

 

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

 


實操:(自定義路信息存儲管理)

1、自定義一個路由信息描述類 存儲路由信息的對象,繼承 RouteDefinition  根據自己的需求可自行擴展增加屬性字段(使用數據庫對應字段,可自行靈活的擴展屬性字段),數據庫中存儲此 類 對象

/**
 * 擴展此類支持序列化路由,(可將路由信息添加到數據庫及Redis)
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class RouteDefinitionInDb extends RouteDefinition implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 路由名稱
     */
    private String routeName;
}

2、路由數據的:增、刪、讀 的類(這里初始化加載路由信息也放在同一個類中處理,實際開發中可自行拆解,這里的示例 代碼並沒有處理 路由存儲 MySQL 和 Redis ,讀者請自行實現

/**
 * 自定義路由系列化加載配置,負責對路由信息的讀、寫、刪除的方法
 */
@Slf4j
@Component
public class RouteDefinitionWriterAndReader implements RouteDefinitionRepository {
 
    /**
     * 這個是 Gateway 默認加載路由信息的配置(示例代碼並沒有使用Redis 和 MySQL ,讀者自行變更為 MySQL 中存儲,啟動時加載到 Redis 中即可)
     */
    @Autowired
    private GatewayProperties properties;
 
    /**
     * 后續改為 Redis 和 數據庫方式存儲 路由信息,此處改為相應的 RedisTemplate
     */
    private static volatile Map<String, RouteDefinitionInDb> ROUTES_MAP = new ConcurrentHashMap<>(1 << 4);
 
 
    /**
     * 初始化配置文件中的路由到自定義容器中,后續直接從數據庫中存儲到 Redis 去
     */
    @PostConstruct
    public void initRoute() {
        properties.getRoutes().forEach(route -> {
            RouteDefinitionInDb routeInDb = new RouteDefinitionInDb();
            BeanUtil.copyProperties(route, routeInDb);
            // 暫時沒有路由名稱,ID代替
            routeInDb.setRouteName("路由名稱:" + route.getId());
            log.info("加載路由:{},路由信息為:{}", routeInDb.getRouteName(), JSON.toJSONString(routeInDb));
            ROUTES_MAP.put(route.getId(), routeInDb);
        });
        log.info("路由加載完畢,路由數量為:{}", ROUTES_MAP.size());
    }
 
 
    /**
     * 獲取路由配置
     *
     * @return
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        // RouteDefinition 后續可改為數據庫存儲的對象 RouteDefinitionInDb
        List<RouteDefinitionInDb> routesInDb = ROUTES_MAP.entrySet().stream()
                .map(routeEntry -> routeEntry.getValue())
                .collect(Collectors.toList());
        return Flux.fromIterable(routesInDb);
    }
 
 
    /**
     * 保存路由配置(暫時未實現具體邏輯)
     *
     * @param route
     * @return
     */
    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        // 暫時使用默認方式配置,即使用配置文件讀取的方式配置,后續改為Redis存儲
        Mono<Void> mono = route.flatMap(r -> {
            RouteDefinitionInDb routeInDb = new RouteDefinitionInDb();
            BeanUtil.copyProperties(r, routeInDb);
            ROUTES_MAP.put(routeInDb.getId(), routeInDb);
            return Mono.empty();
        });
        return mono;
    }
 
    /**
     * 刪除路由配置(暫時未實現具體邏輯)
     *
     * @param routeId
     * @return
     */
    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        // 時使用默認方式配置,即使用配置文件讀取的方式配置,后續改為Redis存儲
        return routeId.flatMap(id -> {
            if (ROUTES_MAP.containsKey(id)) {
                ROUTES_MAP.remove(id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
        });
    }
}

 


免責聲明!

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



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