本文采用的Spring cloud为2.1.8RELEASE,version=Greenwich.SR3
本文基于前面的几篇Spring cloud Gateway文章的实现。
参考html
写了几篇关于Spring Cloud Gateway的文章后发现,Gateway涉及的知识范围太广了,真是深入体会了“一入Spring cloud深似海”。java
现实生产环境中,使用Spring Cloud Gateway都是做为全部流量的入口,为了保证系统的高可用,尽可能避免系统的重启,因此须要Spring Cloud Gateway的动态路由来处理。以前的文章《Gateway路由网关教程》提供的路由配置,在系统启动时候,会将路由配置和规则加载到内存当中,没法作到不重启服务就能够动态的新增、修改、删除内存中的路由配置和规则。react
Spring Cloud Gateway源码中提供了GatewayControllerEndpoint类来修改路由配置,可是官方文档好像并无作详细的使用说明,只是简单介绍了几个简单的api接口。感兴趣的小伙伴能够先查看官方文档(第11章节 Actuator API)。git
引致官方文档:github
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.web
在原来spring-gateway的基础上增长spring-boot-starter-actuator依赖redis
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
management: endpoints: web: exposure: include: "*"
配置说明:management.endpoints.web.exposure.include 暴露全部的gateway端点spring
启动服务,访问http://localhost:8100/actuator/gateway/routes,这是咱们能够看到全部的routes信息。json
咱们也能够访问单个路由信息:http://localhost:8100/actuator/gateway/routes/CompositeDiscoveryClient_EUREKA-CLIENTapi
显示以下:
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,若是格式不正确会报错。