目录

  • 背景
  • 本文开发环境介绍
  • 新增路由
  • 实现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

gateway 修改loadbalancer gateway动态修改路由_动态

总结

以上示例通过代码的方式对路由进行了动态新增、修改和删除,有了这些动态的操作,基本上可以满足我们很多实际的业务场景了,比如在配置中心修改路由规则后,可以使用RouteDefinitionWriter动态刷新路由,勿需重启Gateway服务;也可以通过构造PropertiesRouteDefinitionLocator方式,结合实际业务规则,修改静态配置的路由。