Spring Cloud Gateway 是 Spring Cloud 的一個全新項目,該項目是基於 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發的網關,它旨在為微服務架構提供一種簡單有效的統一的 API 路由管理方式。Spring Cloud Gateway 作為 Spring Cloud 生態系統中的網關,目標是替代 Netflix Zuul,其不僅提供統一的路由方式,並且基於 Filter 鏈的方式提供了網關基本的功能,例如:安全,監控/指標,和限流。
相應的入門demo網上很多,我們這邊一筆帶過
1.引入pom依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
2.application.yml 配置文件
server: servlet: context-path: / port: 18889 spring: application: name: client-gateway cloud: gateway: discovery: locator: enabled: false #表明gateway開啟服務注冊和發現的功能,並且spring cloud gateway自動根據服務發現為每一個服務創建了一個router,# 這個router將以服務名開頭的請求路徑轉發到對應的服務 lower-case-service-id: true #將請求路徑上的服務名配置為小寫(因為服務注冊的時候,向注冊中心注冊時將服務名轉成大寫的了,比如以/service-hi/*的請求路徑被路由轉發到服務名為service-hi的服務上 routes: - id: test-id uri: lb://client-manage order: -1 predicates: - Path=/api2/** filters: - StripPrefix=1
- id:我們自定義的路由 ID,保持唯一
- uri:目標服務地址
- predicates:路由條件,Predicate 接受一個輸入參數,返回一個布爾值結果。該接口包含多種默認方法來將 Predicate 組合成其他復雜的邏輯(比如:與,或,非)。
- filters:過濾規則,
上面那段路由配置表示所有已包含 /api2/ 的url都會被路由到 client-manage服務,StripPrefix=1表示路由時會去除/api2/。
我們也可以使用api的方式配置
@Bean public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) { // @formatter:off return builder.routes () .route (r -> r.path ("/api2/**") .filters (f -> f.stripPrefix (1)) .uri ("lb://client-manage") .order (0) .id ("client-manage") ) .build (); }
這兩種配置效果是一致。
路由擴展
基於上面兩種配置,我們的網關已經具有了路由的功能了。但是這里還不夠工程化。設想下,現在我有上百個路由信息,配置文件或者api的形式去配置必然會導致可讀性的缺失。同時我還想實現不停機的增加路由。這里就引入了動態增加路由的概念。翻看Gateway的代碼,發現Gateway代碼本身就支持動態增加路由,相關代碼在 org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint#save
入參的 RouteDefinition 是對路由的封裝,與上文的配置文件一一相對應。最終實際調用的是 RouteDefinitionWriter的save方法。
基於上述代碼,我們要做以下幾件事情
1.定義外部存儲文件中的RouteDefinition的數據結構
2.啟動時自動讀取路由信息寫入內存中。
單個路由的添加
@PostMapping(value = "addRoute") @ResponseBody public String addRoute() throws URISyntaxException{ RouteDefinition routeDefinition=new RouteDefinition(); routeDefinition.setId("test-id"); List<PredicateDefinition> predicates=new ArrayList<>(); PredicateDefinition definition=new PredicateDefinition(); //注意name definition.setName("Path"); definition.addArg("pattern","/api2/**"); predicates.add(definition); routeDefinition.setPredicates(predicates); List<FilterDefinition> filters =new ArrayList<>(); FilterDefinition filterDefinition = new FilterDefinition(); //注意name filterDefinition.setName("StripPrefix"); filterDefinition.addArg("parts","1"); filters.add(filterDefinition); routeDefinition.setFilters(filters); URI uri = new URI("lb://client-manage"); routeDefinition.setUri(uri); routeDefinition.setOrder(0); String save = routeService.add(routeDefinition); System.out.println(save); return ""; }
為確保功能的實現,我們先寫死一個配置嘗試用這種方式配置路由。這里注意兩點
PredicateDefinition配置
PredicateDefinition的name代表每一個工廠類,只能從以下選擇


addArgs的key是相應方法參數名稱
public GatewayFilterSpec stripPrefix(int parts) { return filter(getBean(StripPrefixGatewayFilterFactory.class) .apply(c -> c.setParts(parts))); }
FilterDefinition配置
FilterDefinition和PredicateDefinition類似,name從以下選擇
配置文件讀取
單個配置生效后我們開始外部文件的形式去配置,這里為了便捷我依然從項目配置文件讀取。實際我們可以把配置放到數據庫,緩存。
新建api.properties文件
新增配置
api.methods.api2={"predicateDefinition":[{"predicateValue":"/api2/**","name":"Path","predicateKey":"pattern"}],"id":"test_id","uri":"lb://client-manage","filterDefinition":[{"filterKey":"parts","filterValue":"1","name":"StripPrefix"}],"order":"0"}
定義RouteDefines讀取配置文件
@Configuration @PropertySource("classpath:api.properties") @ConfigurationProperties(prefix = "api") public class RouteDefines { public Map<String, String> methods = new HashMap<>(); public Map<String, String> getMethods() { return methods; } public void setMethods(Map<String, String> methods) { this.methods = methods; } }
定義InitRouteApplication初始化路由信息寫入內存
package com.hdkj.client.gateway; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.hdkj.client.gateway.configuration.RouteDefines; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.cloud.gateway.filter.FilterDefinition; import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.stereotype.Component; /** * @author Xu.Minzhe * @version V1.0 * @package com.hdkj.client.gateway * @class: InitRouteApplication.java * @description: 初始化路由信息 * @Date 2019-04-25 10:15 */ @Component public class InitRouteApplication implements ApplicationRunner { private static final Logger logger = LoggerFactory.getLogger(InitRouteApplication.class); @Autowired private RouteDefines routeDefines; @Autowired private DynamicRouteService routeService; @Override public void run(ApplicationArguments args) throws Exception { Map<String, String> methods = routeDefines.getMethods(); methods.values().stream().forEach(x->{ try { System.out.println("配置文件讀取的信息"+x); JSONObject jsonObject = JSONObject.parseObject(x); //組裝RouteDefinition RouteDefinition routeDefinition = getRouteDefinition(jsonObject); //路由信息寫入 String save = routeService.add(routeDefinition); } catch (Exception e) { logger.error("[路由初始化] 異常",e); } }); } /** * 組裝RouteDefinition * @param jsonObject * @return * @throws URISyntaxException */ private RouteDefinition getRouteDefinition(JSONObject jsonObject) throws URISyntaxException { RouteDefinition routeDefinition=new RouteDefinition(); routeDefinition.setId(jsonObject.getString("id")); List<PredicateDefinition> predicateList = getPredicateList(jsonObject); routeDefinition.setPredicates(predicateList); List<FilterDefinition> filterDefinition1 = getFilterDefinition(jsonObject); routeDefinition.setFilters(filterDefinition1); URI uri = new URI(jsonObject.getString("uri")); routeDefinition.setUri(uri); routeDefinition.setOrder(jsonObject.getIntValue("order")); return routeDefinition; } /** * 解析json 獲得PredicateList * @param jsonObject * @return */ private List<PredicateDefinition> getPredicateList(JSONObject jsonObject) { JSONArray predicateDefinition = jsonObject.getJSONArray("predicateDefinition"); List<PredicateDefinition> predicates=new ArrayList<>(); predicateDefinition.stream().forEach(predicate->{ JSONObject jsonObject1 = JSONObject.parseObject(predicate.toString()); PredicateDefinition definition=new PredicateDefinition(); definition.setName(jsonObject1.getString("name")); definition.addArg(jsonObject1.getString("predicateKey"),jsonObject1.getString("predicateValue")); predicates.add(definition); }); return predicates; } /** * 解析json 獲得FilterDefinitionList * @param jsonObject * @return */ private List<FilterDefinition> getFilterDefinition(JSONObject jsonObject) { JSONArray predicateDefinition = jsonObject.getJSONArray("filterDefinition"); List<FilterDefinition> predicates=new ArrayList<>(); predicateDefinition.stream().forEach(predicate->{ JSONObject jsonObject1 = JSONObject.parseObject(predicate.toString()); FilterDefinition definition=new FilterDefinition(); definition.setName(jsonObject1.getString("name")); definition.addArg(jsonObject1.getString("filterKey"),jsonObject1.getString("filterValue")); predicates.add(definition); }); return predicates; } }
這里DynamicRouteService是路由寫入實現類
@Component public class DynamicRouteService implements ApplicationEventPublisherAware { @Autowired private RouteDefinitionWriter routeDefinitionWriter; private ApplicationEventPublisher publisher; /** * 增加路由 * @param definition * @return */ public String add(RouteDefinition definition) { routeDefinitionWriter.save(Mono.just(definition)).subscribe(); this.publisher.publishEvent(new RefreshRoutesEvent(this)); return "success"; } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.publisher = applicationEventPublisher; } }
以上便是路由加載的實現。至於動態新增路由,有了以上的代碼,實現也是相當簡單了。這里不再敘述。