Spring Cloud Gateway 是 Spring Cloud 的一個全新項目,該項目是基於 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發的網關,它旨在爲微服務架構提供一種簡單有效的統一的 API 路由管理方式。Spring Cloud Gateway 做爲 Spring Cloud 生態系統中的網關,目標是替代 Netflix Zuul,其不只提供統一的路由方式,而且基於 Filter 鏈的方式提供了網關基本的功能,例如:安全,監控/指標,和限流。java
相應的入門demo網上不少,咱們這邊一筆帶過spring
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 配置文件 json
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
上面那段路由配置表示全部已包含 /api2/ 的url都會被路由到 client-manage服務,StripPrefix=1表示路由時會去除/api2/。api
咱們也可使用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方法。架構
基於上述代碼,咱們要作如下幾件事情app
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的name表明每個工廠類,只能從如下選擇
addArgs的key是相應方法參數名稱
public GatewayFilterSpec stripPrefix(int parts) { return filter(getBean(StripPrefixGatewayFilterFactory.class) .apply(c -> c.setParts(parts))); }
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; } }
以上即是路由加載的實現。至於動態新增路由,有了以上的代碼,實現也是至關簡單了。這裏再也不敘述。