前几篇已经了解了springboot集成Gateway & Nacos。在前不久的文章里,服务路由的配置时硬编码的实现的,这种配置方式,在启动网关服务后,将无法修改路由配置,若有新服务上线的话,则需要重新部署网关服务。
Gateway路由有两种实现方式
yml配置文件
routes:
- id: gateway-client
uri: lb://gateway-client
predicates:
- Path=/gclient/**
filters:
- AddRequestHeader=X-Request-Token, 2020ABC
- AddResponseHeader=X-Response-Token, 2020ABC
基于bean加载路由
@Bean
public RouteLocator addLocator(RouteLocatorBuilder routeLocatorBuilder){
return routeLocatorBuilder.routes()
.route("gateway-client", r -> r.path("/gclient/**")
.filters(f->f.addRequestParameter("X-Request-Token","2020ABC"))
.uri("lb://gateway-client")).build();
}
以上两种方式就不符合生产环境的使用。
动态路由
Gateway网关服务在启动的时候,配置的路由信息会加载到内存中,路由信息封装对象为 RouteDefinition ,配置多个RouteDefinition组成网关的路由网络。
@Validated
public class RouteDefinition {
private String id;
@NotEmpty
@Valid
private List<PredicateDefinition> predicates = new ArrayList<>();
@Valid
private List<FilterDefinition> filters = new ArrayList<>();
@NotNull
private URI uri;
private int order = 0;
}
还有一个核心类 RouteDefinitionWriter ,路由配置的处理主要是 RouteDefinitionWriter 类型的bean完成的,为了让配置立即生效,还要用applicationEventPublisher发布进程内消息。
public interface RouteDefinitionWriter {
Mono<Void> save(Mono<RouteDefinition> route);
Mono<Void> delete(Mono<String> routeId);
}
简单实现
在生产环境中,我们的路由信息会保存在redis缓存中,或者是mysql等数据库中,下面简单实现gateway动态路由。
工程中整合了gateway、nacos,基础配置基于以前的文章:东小西:springboot集成Gateway & Nacos「再来一刀」。
暴露网关服务端点
暴露端点,即可以查看GATEWAY有哪些端点。
① 添加pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
② yml添加配置
management:
endpoints:
web:
exposure:
include: '*'
exclude: env
endpoint:
health:
show-details: always
③ 通过如下两个url进行访问(端口根据以及ip根据自己设定进行调整)
过滤器模型类
public class MyFilterDefinition {
private String name;
private Map<String,String> args = new HashMap<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getArgs() {
return args;
}
public void setArgs(Map<String, String> args) {
this.args = args;
}
}
路由断言模型类
public class MyPredicateDefinition {
private String name;
private Map<String,String> args = new HashMap<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getArgs() {
return args;
}
public void setArgs(Map<String, String> args) {
this.args = args;
}
}
路由模型类
public class MyRouteDefinition {
private String id;
private List<MyFilterDefinition> filters = new ArrayList<>();
private List<MyPredicateDefinition> predicates = new ArrayList<>();
private String uri;
private int order = 0;
public RouteDefinition getRouteDefinition(){
RouteDefinition definition = new RouteDefinition();
definition.setId(this.getId());
definition.setOrder(this.getOrder());
//设置断言
List<PredicateDefinition> pdList = new ArrayList<>();
List<MyPredicateDefinition> myPredicateDefinitionList = this.getPredicates();
for (MyPredicateDefinition gpDefinition: myPredicateDefinitionList) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setArgs(gpDefinition.getArgs());
predicate.setName(gpDefinition.getName());
pdList.add(predicate);
}
definition.setPredicates(pdList);
//设置过滤器
List<FilterDefinition> filters = new ArrayList();
List<MyFilterDefinition> gatewayFilters = this.getFilters();
for(MyFilterDefinition filterDefinition : gatewayFilters){
FilterDefinition filter = new FilterDefinition();
filter.setName(filterDefinition.getName());
filter.setArgs(filterDefinition.getArgs());
filters.add(filter);
}
definition.setFilters(filters);
URI uri = null;
if(this.getUri().startsWith("http")){
uri = UriComponentsBuilder.fromHttpUrl(this.getUri()).build().toUri();
}else{
uri = URI.create(this.getUri());
}
definition.setUri(uri);
return definition;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<MyFilterDefinition> getFilters() {
return filters;
}
public void setFilters(List<MyFilterDefinition> filters) {
this.filters = filters;
}
public List<MyPredicateDefinition> getPredicates() {
return predicates;
}
public void setPredicates(List<MyPredicateDefinition> predicates) {
this.predicates = predicates;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
}
动态路由实现类
实现ApplicationEventPublisherAware接口。
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
@Resource
private RouteDefinitionWriter routeDefinitionWriter;
@Resource
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
//新增路由
public void add(MyRouteDefinition myRouteDefinition){
/**
* 新增的Actuator Endpoint,刷新路由的时候,先加载路由配置到内存中,
* 然后再使用RefreshRoutesEvent事件刷新内存中路由配置。
*/
routeDefinitionWriter.save(Mono.just(myRouteDefinition.getRouteDefinition())).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
// 删除路由
public void delete(String id){
routeDefinitionWriter.delete(Mono.just(id)).subscribe();
}
}
API接口
@RestController
@RequestMapping("/api")
public class RouteSettingController {
@Resource
private DynamicRouteServiceImpl dynamicRouteService;
@PostMapping("/add")
public String add(@RequestBody MyRouteDefinition myRouteDefinition) {
try{
dynamicRouteService.add(myRouteDefinition);
return "success";
}catch (Exception e){
return "error";
}
}
@PostMapping("delete")
public String delete(String id) {
try{
dynamicRouteService.delete(id);
return "success";
}catch (Exception e){
return "error";
}
}
}
服务测试
① 启动网关服务工程(springboot-gateway-dynamic-server)和测试服务工程(springboot-gateway-client-a),查看nacos注册情况。
② 查看所有路由,访问地址:http://localhost:9001/actuator/gateway/routes
③ 未配置路由情况下,访问测试服务接口。
④ 配置测试服务路由,再次访问测试服务接口
{
"id": "springboot-gateway-client-a",
"uri": "lb://springboot-gateway-client-a",
"order": 1,
"predicates": [{
"name": "Path",
"args": {
"pattern": "/sgca/**"
}
}]
}
⑤ 调用删除路由接口,再次访问测试服务接口。