本文采用的Spring cloud爲2.1.8RELEASE,version=Greenwich.SR3 html
本文基於前面的幾篇Spring cloud Gateway文章的實現。 參考java
寫了幾篇關於Spring Cloud Gateway的文章後發現,Gateway涉及的知識範圍太廣了,真是深入體會了「一入Spring cloud深似海」。react
現實生產環境中,使用Spring Cloud Gateway都是做爲全部流量的入口,爲了保證系統的高可用,儘可能避免系統的重啓,因此須要Spring Cloud Gateway的動態路由來處理。以前的文章《Gateway路由網關教程》提供的路由配置,在系統啓動時候,會將路由配置和規則加載到內存當中,沒法作到不重啓服務就能夠動態的新增、修改、刪除內存中的路由配置和規則。git
Spring Cloud Gateway源碼中提供了GatewayControllerEndpoint類來修改路由配置,可是官方文檔好像並無作詳細的使用說明,只是簡單介紹了幾個簡單的api接口。感興趣的小夥伴能夠先查看官方文檔(第11章節 Actuator API)。github
引致官方文檔:web
The
/gateway
actuator endpoint allows to monitor and interact with a Spring Cloud Gateway application. To be remotely accessible, the endpoint has to be enabled and exposed via HTTP or JMX in the application properties.redis
在原來spring-gateway的基礎上增長spring-boot-starter-actuator依賴spring
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
複製代碼
management:
endpoints:
web:
exposure:
include: "*"
複製代碼
配置說明:management.endpoints.web.exposure.include 暴露全部的gateway端點json
啓動服務,訪問http://localhost:8100/actuator/gateway/routes,這是咱們能夠看到全部的routes信息。api
咱們也能夠訪問單個路由信息: http://localhost:8100/actuator/gateway/routes/CompositeDiscoveryClient_EUREKA-CLIENT顯示以下:
Gateway默認使用的是GatewayControllerEndpoint這個類,GatewayControllerEndpoint又繼承了AbstractGatewayControllerEndpoint類。
提供的方法:(只列具了幾個相關方法,其餘方法小夥們能夠自行查看源碼)
- /gateway/routes 查詢全部路由信息
- /gateway/routes/{id} 根據路由id查詢單個信息
- /gateway/routes/{id} @PostMapping 新增一個路由信息
- /gateway/routes/{id} @DeleteMapping 刪除一個路由信息
咱們可根據/gateway/routes返回的路由信息,來模仿一個@PostMapping請求參數
{
"uri": "http://httpbin.org:80",
"predicates": [
{
"args": {
"pattern": "/ribbon/**"
},
"name": "Path"
}
],
"filters": []
}
複製代碼
這是咱們能夠經過postman來發送一個post請求,如圖所示:
response返回1證實已經插入成功,咱們能夠經過http://localhost:8100/actuator/gateway/routes/查看路由信息,顯示結果以下:
截圖紅框中的信息就是新增長的
咱們能夠直接用postman模擬DeleteMapping請求,http://localhost:8100/actuator/gateway/routes/addroutes
顯示以下:
這時候咱們在訪問http://localhost:8100/actuator/gateway/routes,能夠看到新增長的路由已經被刪除成功了。
基於Spring Cloud Gateway默認方法實現的動態路由我就完成了,在前言中我已經提到了,這種方式是基於jvm內存實現,一旦服務重啓,新增的路由配置信息就是徹底消失了。全部這個時候咱們能夠考慮是否能夠參考GatewayControllerEndpoint這類,本身實現一套動態路由方法,而後將路由信息持久化。
能夠自定義實體類,我這裏偷了一個懶,直接用了Gateway的RouteDefinition類,感興趣的小夥伴能夠參考RouteDefinition類本身擴展,而後寫一個Convert類將自定義的類轉換成RouteDefinition就能夠了。
我這邊採用redis作爲路由配置的信息的持久層,因此寫了一個RedisRouteDefinitionRepository。
package spring.cloud.demo.spring.gateway.component;
import com.google.common.collect.Lists;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import spring.cloud.demo.spring.gateway.redis.RedisUtils;
import spring.cloud.demo.spring.gateway.util.JsonUtils;
import javax.annotation.Resource;
import java.util.List;
/** * @auther: maomao * @DateT: 2019-11-03 */
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
//存儲的的key
private final static String KEY = "gateway_dynamic_route";
@Resource
private RedisUtils redisUtils;
/** * 獲取路由信息 * @return */
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
List<RouteDefinition> gatewayRouteEntityList = Lists.newArrayList();
redisUtils.hgets(KEY).stream().forEach(route -> {
RouteDefinition result = JsonUtils.parseJson(route.toString(), RouteDefinition.class);
gatewayRouteEntityList.add(result);
});
return Flux.fromIterable(gatewayRouteEntityList);
}
/** * 新增 * @param route * @return */
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(routeDefinition -> {
redisUtils.hset(KEY, routeDefinition.getId(), JsonUtils.toString(routeDefinition));
return Mono.empty();
});
}
/** * 刪除 * @param routeId * @return */
@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
if (redisUtils.hHashKey(KEY, id)) {
redisUtils.hdel(KEY, id);
return Mono.empty();
}
return Mono.defer(() -> Mono.error(new NotFoundException("route definition is not found, routeId:" + routeId)));
});
}
}
複製代碼
package spring.cloud.demo.spring.gateway.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import spring.cloud.demo.spring.gateway.service.GatewayDynamicRouteService;
import javax.annotation.Resource;
/** * 自定義動態路由 * @auther: maomao * @DateT: 2019-11-03 */
@RestController
@RequestMapping("/gateway")
@Slf4j
public class GatewayDynamicRouteController {
@Resource
private GatewayDynamicRouteService gatewayDynamicRouteService;
@PostMapping("/add")
public String create(@RequestBody RouteDefinition entity) {
int result = gatewayDynamicRouteService.add(entity);
return String.valueOf(result);
}
@PostMapping("/update")
public String update(@RequestBody RouteDefinition entity) {
int result = gatewayDynamicRouteService.update(entity);
return String.valueOf(result);
}
@DeleteMapping("/delete/{id}")
public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
return gatewayDynamicRouteService.delete(id);
}
}
複製代碼
package spring.cloud.demo.spring.gateway.service;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import spring.cloud.demo.spring.gateway.component.RedisRouteDefinitionRepository;
import javax.annotation.Resource;
/** * @auther: maomao * @DateT: 2019-11-03 */
@Service
public class GatewayDynamicRouteService implements ApplicationEventPublisherAware {
@Resource
private RedisRouteDefinitionRepository redisRouteDefinitionRepository;
private ApplicationEventPublisher applicationEventPublisher;
/** * 增長路由 * @param routeDefinition * @return */
public int add(RouteDefinition routeDefinition) {
redisRouteDefinitionRepository.save(Mono.just(routeDefinition)).subscribe();
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
return 1;
}
/** * 更新 * @param routeDefinition * @return */
public int update(RouteDefinition routeDefinition) {
redisRouteDefinitionRepository.delete(Mono.just(routeDefinition.getId()));
redisRouteDefinitionRepository.save(Mono.just(routeDefinition)).subscribe();
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
return 1;
}
/** * 刪除 * @param id * @return */
public Mono<ResponseEntity<Object>> delete(String id) {
return redisRouteDefinitionRepository.delete(Mono.just(id)).then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
.onErrorResume(t -> t instanceof NotFoundException, t -> Mono.just(ResponseEntity.notFound().build()));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
複製代碼
application.yml配置要暴漏Gateway的全部端點,能夠看考以前的配置信息。
啓動Spring cloud Gateway服務,先訪問http://localhost:8100/actuator/gateway/routes,查看已有的路由配置信息。而後咱們用postman請求add方法,http://localhost:8100/gateway/add,若是所示:
注意截圖中紅框的內容。證實已經新增成功。
這時咱們在訪問http://localhost:8100/actuator/gateway/routes查看結果。若是所示:
同理咱們能夠訪問update和delete方法,我這裏就不過多描述了。
自定義動態路由核心原理其實就要重寫網關模塊,也就是以前提到的RedisRouteDefinitionRepository類。我這裏偷懶沒有從新定義對應的實體類,這裏須要注意的是,傳入參數必定要按照application.yml中配置的格式,而後轉成json,若是格式不正確會報錯。