SpringGateway 动态路由
文章根据大佬博客合成,再写一遍怕他们删除我找不到
网关动态路由实现
路由源码解析
RewritePath
1 背景介绍
常用的网关路由配置一般有两种:
(1)配置在yaml文件里,修改时通过nacos修改,能修改,但是路由数据源只能来自文件
(2)配置在代码里,修改只能修改源码
需要一种非常灵活的修改,路由信息存在一个地方(数据库任何能取到的地方),需要修改时只需要修改数据源。
2 代码展示
参考:网关动态路由实现
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* 动态路由配置类
*/
@Service
@Slf4j
public class DynamicRouteConfig implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routedefinitionWriter;
private ApplicationEventPublisher publisher;
private final List<String> ROUTE_LIST = new LinkedList<>();
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
publisher = applicationEventPublisher;
}
@PostConstruct
public void dynamicRouteListener() {
try {
log.info("dynamic route action...");
publisher();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 发布路由
*/
private void publisher() {
// 先清除路由
clearRoute();
try {
// 路由定义列表,自定义路由可以在这里被装填进这个列表 下面用假数据做个示范
List<RouteDefinition> routeDefinitions = new ArrayList<>();
// 单个路由
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId("champagne-user-dev");
routeDefinition.setUri(new URI("www.baidu.com"));
// 过滤定义
List<FilterDefinition> filterDefinitions = new ArrayList<>();
filterDefinitions.add(new FilterDefinition("Path=/baidu/**"));
routeDefinition.setFilters(filterDefinitions);
// 断言定义
List<PredicateDefinition> predicateDefinitions = new ArrayList<>();
predicateDefinitions.add(new PredicateDefinition("RewritePath=/baidu(?<segment>/?.*), $\\{segment}"));
routeDefinition.setPredicates(predicateDefinitions);
routeDefinitions.add(routeDefinition);
for (RouteDefinition definition : routeDefinitions) {
addRoute(definition);
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
/**
* 添加路由
*
* @param def
* @return
*/
public Boolean addRoute(RouteDefinition def) {
try {
routedefinitionWriter.save(Mono.just(def)).subscribe();
ROUTE_LIST.add(def.getId());
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
/**
* 清空路由
*
* @return
*/
public Boolean clearRoute() {
for (String id : ROUTE_LIST) {
routedefinitionWriter.delete(Mono.just(id)).subscribe();
}
ROUTE_LIST.clear();
return Boolean.FALSE;
}
}
3 具体解析
3.1 源码解析
参考:路由源码解析
3.1.1 InMemoryRouteDefinitionRepository 内存路由定义仓库
实现了RouteDefinitionRepository接口,RouteDefinitionRepository分别实现了RouteDefinitionLocator和RouteDefinitionWriter
3.1.2 RouteDefinitionLocator 路由定义定位器
定义了获取路由的方法
public interface RouteDefinitionLocator {
Flux<RouteDefinition> getRouteDefinitions();
}
3.1.3 RouteDefinitionWriter 路由定义编写器
定义了保存和删除路由的方法
public interface RouteDefinitionWriter {
Mono<Void> save(Mono<RouteDefinition> route);
Mono<Void> delete(Mono<String> routeId);
}
3.1.4 AbstractGatewayControllerEndpoint
定义了修改,保存路由的对外接口 这里可以看到官方如何使用RouteDefinitionRepository的方法
//添加路由
@PostMapping("/routes/{id}")
@SuppressWarnings("unchecked")
public Mono<ResponseEntity<Object>> save(@PathVariable String id,
@RequestBody RouteDefinition route) {
return Mono.just(route).filter(this::validateRouteDefinition)
.flatMap(routeDefinition -> this.routeDefinitionWriter
.save(Mono.just(routeDefinition).map(r -> {
r.setId(id);
log.debug("Saving route: " + route);
return r;
}))
.then(Mono.defer(() -> Mono.just(ResponseEntity
.created(URI.create("/routes/" + id)).build()))))
.switchIfEmpty(
Mono.defer(() -> Mono.just(ResponseEntity.badRequest().build())));
}
// 删除路由
@DeleteMapping("/routes/{id}")
public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
return this.routeDefinitionWriter.delete(Mono.just(id))
.then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
.onErrorResume(t -> t instanceof NotFoundException,
t -> Mono.just(ResponseEntity.notFound().build()));
}
// 获取所有路由
@GetMapping("/routedefinitions")
public Flux<RouteDefinition> routesdef() {
return this.routeDefinitionLocator.getRouteDefinitions();
}
3.1.5 自定义路由 RouteDefinitionRepository
在网关自动配置类GatewayAutoConfiguration可以看到,默认使用InMemoryRouteDefinitionRepository实现类,我们可以重新写一个实现类替换他。
@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
return new InMemoryRouteDefinitionRepository();
}
3.2 RewritePath解析
参考:RewritePath
前端使用不同的path访问,网关根据path断言判断后,RewritePath再次重写地址
```java
predicateDefinitions.add(new PredicateDefinition("RewritePath=/baidu(?<segment>/?.*), $\\{segment}"));
4 网关动态路由实现方式
4.1 重写RouteDefinitionRepository实现类
在项目加载时通过修改 getRouteDefinitions() 方法,再利用 save(Mono route) 保存
4.2 利用RouteDefinitionWriter对原有路由列表修改
利用 save(Mono route) 和 delete(Mono routeId) 对生成好的路由进行修改