目录
- 背景
- 本文开发环境介绍
- 新增路由
- 实现ApplicationEventPublisherAware接口
- 验证新路由
- 注意
- 修改路由
- 源码解析
- 构建类型为PropertiesRouteDefinitionLocator的Bean
- 验证修改后的路由
- 删除路由
- 重新修改上面的RouteDefinitionLocatorAutoConfiguration类
- 验证删除后的效果
- 总结
以上文章演示了Gateway的部分功能的自定义开发,我们在实际业务中,很多转发的路由并不是通过配置文件提前配置好的,可能是通过配置中心获取并且会动态变化,新开发好的API需要挂到API网关上,希望通过仅修改配置解决,API网关不需要重启,那么我们就需要基于Gateway动态增加、修改、删除路由的能力。
本文开发环境介绍
开发依赖 | 版本 |
Spring Boot | 2.7.0 |
Spring Cloud | 2021.0.1 |
Spring Cloud Alibaba | 2021.0.1.0 |
本文继续接着上一篇文章往下讲,代码和配置都是接着上一篇,如有看不懂的地方可以把前面关于Gateway的内容过目一下。
新增路由
实现ApplicationEventPublisherAware接口
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
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.cloud.gateway.route.RouteLocator;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.net.URI;
import java.util.ArrayList;
@RequiredArgsConstructor
@Configuration
public class RouteAutoConfiguration implements ApplicationEventPublisherAware {
private final RouteDefinitionWriter routeDefinitionWriter;
private final RouteLocator routeLocator;
@Setter
private ApplicationEventPublisher applicationEventPublisher;
@SneakyThrows
@PostConstruct
public void init() {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId("manual-route-1");
routeDefinition.setUri(new URI("https://news.baidu.com/"));
routeDefinition.setPredicates(new ArrayList<PredicateDefinition>() {{
add(new PredicateDefinition("Path=/baidu/news/**"));
}});
routeDefinition.setFilters(new ArrayList<FilterDefinition>() {{
add(new FilterDefinition("StripPrefix=2"));
}});
routeDefinition.setOrder(-1);
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));
}
}
代码新增了一条新的路由转发规则,把/baidu/news/
转发到https://news.baidu.com/
验证新路由
在浏览器输入http://localhost:8081/baidu/news
注意
在配置文件中有一个/baidu
的转发,新增的/baidu/news/
是/baidu
子路径,Gateway会根据顺序进行匹配,先匹配到哪条符合条件的规则就会执行哪个规则,所以新增路由的代码中有一句routeDefinition.setOrder(-1);
,这个顺序放在了配置文件中/baidu
的前面。
修改路由
在第①篇文章中,演示了Demo过滤器的配置,那么我们现在尝试通过编码的方式动态修改已经配置好的路由规则,把/baidu/
的Demo过滤器的第3个参数进行修改
源码解析
- 在yml文件中配置的路由规则会映射到GatewayProperties这个类中
- 根据GatewayProperties类会生成PropertiesRouteDefinitionLocator这个Bean
- 具体的Bean构造可以查看GatewayAutoConfiguration这个类的源码
@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
return new PropertiesRouteDefinitionLocator(properties);
}
看到上面这段代码,我们就可以生成我们自己的PropertiesRouteDefinitionLocator
构建类型为PropertiesRouteDefinitionLocator的Bean
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Optional;
@Configuration
public class RouteDefinitionLocatorAutoConfiguration {
@Bean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
Optional<RouteDefinition> optional = properties.getRoutes().stream().filter(r->r.getId().equals("baidu")).findAny();
if(optional.isPresent()) {
Optional<FilterDefinition> filterOptional = optional.get().getFilters().stream().filter(f->f.getName().equals("Demo")).findAny();
filterOptional.get().getArgs().put(NameUtils.generateName(2), "manual");
}
return new PropertiesRouteDefinitionLocator(properties);
}
}
上面的代码把配置文件中的world2修改成了manual
验证修改后的路由
- 在浏览器输入
http://localhost:8081/baidu
- 在控制台可以看到如下输出
删除路由
重新修改上面的RouteDefinitionLocatorAutoConfiguration类
@Configuration
public class RouteDefinitionLocatorAutoConfiguration {
@Bean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
Optional<RouteDefinition> optional = properties.getRoutes().stream().filter(r->r.getId().equals("baidu")).findAny();
if(optional.isPresent()) {
Optional<FilterDefinition> filterOptional = optional.get().getFilters().stream().filter(f->f.getName().equals("Demo")).findAny();
filterOptional.get().getArgs().put(NameUtils.generateName(2), "manual");
}
Optional<RouteDefinition> optional2 = properties.getRoutes().stream().filter(r->r.getId().equals("demoSwagger")).findAny();
if(optional2.isPresent()) {
properties.getRoutes().remove(optional2.get());
}
return new PropertiesRouteDefinitionLocator(properties);
}
}
代码中把demoSwagger这个路由删除了
验证删除后的效果
在浏览器再次输入http://localhost:8081/swagger-ui/index.html
总结
以上示例通过代码的方式对路由进行了动态新增、修改和删除,有了这些动态的操作,基本上可以满足我们很多实际的业务场景了,比如在配置中心修改路由规则后,可以使用RouteDefinitionWriter动态刷新路由,勿需重启Gateway服务;也可以通过构造PropertiesRouteDefinitionLocator方式,结合实际业务规则,修改静态配置的路由。