【加精】Spring Cloud GateWay动态路由配置

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 【加精】Spring Cloud GateWay动态路由配置

目录

  • GateWay配置
  • 在mysql定义表gateway_define, 表结构如下面的GatewayDefine实体类:
  • 定义repository和service,采用JPA实现
  • 定义MysqlRouteDefinitionRepository类,实现RouteDefinitionRepository接口的getRouteDefinitions方法,获取从数据库里面装载的路由配置,当然还有save和delete其他方法。
  • 在启动类GatewayServiceApplication中添加两个Bean。 添加ApplicationStartup类,在Spring Boot启动时装载路由配置信息, 说明看注释:
  • 其他
  • 最后
    • 后记

    Spring Cloud Gateway是由spring官方基于Spring5.0,Spring Boot2.0,Project Reactor等技术开发的网关,目的是代替原先版本中的Spring Cloud Netfilx Zuul。目前Netfilx已经开源了Zuul2.0,但Spring 没有考虑集成,而是推出了自己开发的Spring Cloud GateWay。

    该项目提供了一个构建在Spring Ecosystem之上的API网关,旨在提供一种简单而有效的途径来发送API,并向他们提供交叉关注点:例如:安全性,监控/指标和弹性。在这里废话少说,直接把我实现动态自定义路由的方法托出,共大家参考。由于水平有限,难免有不当或者错误之处,请大家指正,谢谢。

    GateWay配置

    一般的,我们如果使用Spring Cloud GateWay进行配置,类似于下面的样子:

    12345678910111213
    spring:  cloud:    gateway:      discovery:        locator:          enabled: true      routes:      - id: sample-service-a        uri: lb://SAMPLE-SERVICE-A-HA        predicates:        - Path=/customeradd/**        filters:        - RewritePath=/customeradd,/customer/add

    spring:
    cloud:
    gateway:
    discovery:
    locator:
    enabled: true
    routes:
    - id: sample-service-a
    uri: lb://SAMPLE-SERVICE-A-HA
    predicates:
    - Path=/customeradd/**
    filters:
    - RewritePath=/customeradd,/customer/add

    当我们要新增或者改变一个网关路由时,我们不得不停止网关服务,修改配置文件,保存再重新启动网关服务,这样才能让我们新的设置生效。

    设想一样,如果是在生产环境,为了一个小小的路由变更,这样的停止再重启恐怕谁也受不了吧。接下来,看看我们怎么能做到动态配置网关路由,让网关路由配置在服务不需要重启的情况生效。

    在mysql定义表gateway_define, 表结构如下面的GatewayDefine实体类:

    12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
    @Entity@Table(name = "gateway_define")public class GatewayDefine implements Serializable {    @Id    @GeneratedValue(strategy = GenerationType.AUTO)    private String id;     private String uri;     private String predicates;     private String filters;     public String getId() {        return id;    }     public void setId(String id) {        this.id = id;    }     public String getUri() {        return uri;    }    public void setUri(String uri) {        this.uri = uri;    }    public String getPredicates() {        return this.predicates;    }     public void setPredicates(String predicates) {        this.predicates = predicates;    }     public ListPredicateDefinition getPredicateDefinition() {        if (this.predicates != null) {            ListPredicateDefinition predicateDefinitionList = JSON.parseArray(this.predicates, PredicateDefinition.class);            return predicateDefinitionList;        } else {            return null;        }    }     public String getFilters() {        return filters;    }    public ListFilterDefinition getFilterDefinition() {        if (this.filters != null) {            ListFilterDefinition filterDefinitionList = JSON.parseArray(this.filters, FilterDefinition.class);            return filterDefinitionList;        } else {            return null;        }    }     public void setFilters(String filters) {        this.filters = filters;    }     @Override    public String toString() {        return "GatewayDefine{" +                "id='" + id + '\'' +                ", uri='" + uri + '\'' +                ", predicates='" + predicates + '\'' +                ", filters='" + filters + '\'' +                '}';    }}

    @Entity
    @Table(name = “gateway_define”)
    public class GatewayDefine implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private String id;

    
    private String uri;
    
    private String predicates;
    
    private String filters;
    
    public String getId() {
        return id;
    }
    
    public void setId(String id) {
        this.id = id;
    }
    
    public String getUri() {
        return uri;
    }
    public void setUri(String uri) {
        this.uri = uri;
    }
    public String getPredicates() {
        return this.predicates;
    }
    
    public void setPredicates(String predicates) {
        this.predicates = predicates;
    }
    
    public ListPredicateDefinition getPredicateDefinition() {
        if (this.predicates != null) {
            ListPredicateDefinition predicateDefinitionList = JSON.parseArray(this.predicates, PredicateDefinition.class);
            return predicateDefinitionList;
        } else {
            return null;
        }
    }
    
    public String getFilters() {
        return filters;
    }
    public ListFilterDefinition getFilterDefinition() {
        if (this.filters != null) {
            ListFilterDefinition filterDefinitionList = JSON.parseArray(this.filters, FilterDefinition.class);
            return filterDefinitionList;
        } else {
            return null;
        }
    }
    
    public void setFilters(String filters) {
        this.filters = filters;
    }
    
    @Override
    public String toString() {
        return "GatewayDefine{" +
                "id='" + id + '\'' +
                ", uri='" + uri + '\'' +
                ", predicates='" + predicates + '\'' +
                ", filters='" + filters + '\'' +
                '}';
    }
    

    }

    其中:id为Eureka注册的服务名; uri、predicates、filters分别应上面配置文件片段中的predicates和filters(这两个保存的都是json)

    定义repository和service,采用JPA实现

    12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
    @Repositorypublic interface GatewayDefineRepository  extends JpaRepositoryGatewayDefine, String {     @Override    ListGatewayDefine findAll();    @Override    GatewayDefine save(GatewayDefine gatewayDefine);    @Override    void deleteById(String id);    @Override    boolean existsById(String id);} public interface GatewayDefineService {    ListGatewayDefine findAll() throws Exception;    String loadRouteDefinition() throws Exception;    GatewayDefine save(GatewayDefine gatewayDefine) throws Exception;    void deleteById(String id) throws Exception;    boolean existsById(String id)throws Exception; } @Servicepublic class GatewayDefineServiceImpl implements GatewayDefineService {    @Autowired    GatewayDefineRepository gatewayDefineRepository;     @Autowired    private GatewayDefineService gatewayDefineService;     @Autowired    private RouteDefinitionWriter routeDefinitionWriter;     private ApplicationEventPublisher publisher;     @Override    public ListGatewayDefine findAll() throws Exception {        return gatewayDefineRepository.findAll();    }     @Override    public String loadRouteDefinition() {        try {            ListGatewayDefine gatewayDefineServiceAll = gatewayDefineService.findAll();            if (gatewayDefineServiceAll == null) {                return "none route defined";            }            for (GatewayDefine gatewayDefine : gatewayDefineServiceAll) {                RouteDefinition definition = new RouteDefinition();                definition.setId(gatewayDefine.getId());                definition.setUri(new URI(gatewayDefine.getUri()));                ListPredicateDefinition predicateDefinitions = gatewayDefine.getPredicateDefinition();                if (predicateDefinitions != null) {                    definition.setPredicates(predicateDefinitions);                }                ListFilterDefinition filterDefinitions = gatewayDefine.getFilterDefinition();                if (filterDefinitions != null) {                    definition.setFilters(filterDefinitions);                }                routeDefinitionWriter.save(Mono.just(definition)).subscribe();                this.publisher.publishEvent(new RefreshRoutesEvent(this));            }            return "success";        } catch (Exception e) {            e.printStackTrace();            return "failure";        }    }     @Override    public GatewayDefine save(GatewayDefine gatewayDefine) throws Exception {        gatewayDefineRepository.save(gatewayDefine);        return gatewayDefine;    }     @Override    public void deleteById(String id) throws Exception {        gatewayDefineRepository.deleteById(id);    }     @Override    public boolean existsById(String id) throws Exception {        return gatewayDefineRepository.existsById(id);    }}

    @Repository
    public interface GatewayDefineRepository extends JpaRepositoryGatewayDefine, String {

    
    @Override
    ListGatewayDefine findAll();
    @Override
    GatewayDefine save(GatewayDefine gatewayDefine);
    @Override
    void deleteById(String id);
    @Override
    boolean existsById(String id);
    

    }

    public interface GatewayDefineService {
    ListGatewayDefine findAll() throws Exception;
    String loadRouteDefinition() throws Exception;
    GatewayDefine save(GatewayDefine gatewayDefine) throws Exception;
    void deleteById(String id) throws Exception;
    boolean existsById(String id)throws Exception;

    }

    @Service
    public class GatewayDefineServiceImpl implements GatewayDefineService {
    @Autowired
    GatewayDefineRepository gatewayDefineRepository;

    
    @Autowired
    private GatewayDefineService gatewayDefineService;
    
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    
    private ApplicationEventPublisher publisher;
    
    @Override
    public ListGatewayDefine findAll() throws Exception {
        return gatewayDefineRepository.findAll();
    }
    
    @Override
    public String loadRouteDefinition() {
        try {
            ListGatewayDefine gatewayDefineServiceAll = gatewayDefineService.findAll();
            if (gatewayDefineServiceAll == null) {
                return "none route defined";
            }
            for (GatewayDefine gatewayDefine : gatewayDefineServiceAll) {
                RouteDefinition definition = new RouteDefinition();
                definition.setId(gatewayDefine.getId());
                definition.setUri(new URI(gatewayDefine.getUri()));
                ListPredicateDefinition predicateDefinitions = gatewayDefine.getPredicateDefinition();
                if (predicateDefinitions != null) {
                    definition.setPredicates(predicateDefinitions);
                }
                ListFilterDefinition filterDefinitions = gatewayDefine.getFilterDefinition();
                if (filterDefinitions != null) {
                    definition.setFilters(filterDefinitions);
                }
                routeDefinitionWriter.save(Mono.just(definition)).subscribe();
                this.publisher.publishEvent(new RefreshRoutesEvent(this));
            }
            return "success";
        } catch (Exception e) {
            e.printStackTrace();
            return "failure";
        }
    }
    
    @Override
    public GatewayDefine save(GatewayDefine gatewayDefine) throws Exception {
        gatewayDefineRepository.save(gatewayDefine);
        return gatewayDefine;
    }
    
    @Override
    public void deleteById(String id) throws Exception {
        gatewayDefineRepository.deleteById(id);
    }
    
    @Override
    public boolean existsById(String id) throws Exception {
        return gatewayDefineRepository.existsById(id);
    }
    

    }

    注:

    loadRouteDefinition是重点,它从数据库里获取动态定义的路由,最后封装成RouteDefinition 类实例,调用RouteDefinitionWriter 的save方法保存。
    RouteDefinitionWriter是个接口,真正实现的是InMemoryRouteDefinitionRepository类,在InMemoryRouteDefinitionRepository定义了一个SynchronizedMapk, v=”” 类,所有的设置都在这儿保存。/k,

    RouteDefinitionWriter是个接口,真正实现的是InMemoryRouteDefinitionRepository类,在InMemoryRouteDefinitionRepository定义了一个SynchronizedMapk, v=”” 类,所有的设置都在这儿保存。/k,

    定义MysqlRouteDefinitionRepository类,实现RouteDefinitionRepository接口的getRouteDefinitions方法,获取从数据库里面装载的路由配置,当然还有save和delete其他方法。

    12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
    public class MysqlRouteDefinitionRepository implements RouteDefinitionRepository {    @Autowired    private GatewayDefineService gatewayDefineService;    @Override    public FluxRouteDefinition getRouteDefinitions() {        try {            ListGatewayDefine gatewayDefineList = gatewayDefineService.findAll();            MapString, RouteDefinition routes = new LinkedHashMapString, RouteDefinition();            for (GatewayDefine gatewayDefine: gatewayDefineList) {                RouteDefinition definition = new RouteDefinition();                definition.setId(gatewayDefine.getId());                definition.setUri(new URI(gatewayDefine.getUri()));                ListPredicateDefinition predicateDefinitions = gatewayDefine.getPredicateDefinition();                if (predicateDefinitions != null) {                    definition.setPredicates(predicateDefinitions);                }                ListFilterDefinition filterDefinitions = gatewayDefine.getFilterDefinition();                if (filterDefinitions != null) {                    definition.setFilters(filterDefinitions);                }                routes.put(definition.getId(), definition);             }            return Flux.fromIterable(routes.values());        } catch (Exception e) {            e.printStackTrace();            return Flux.empty();        }    }     @Override    public MonoVoid save(MonoRouteDefinition route) {        return route.flatMap(r - {            try {                GatewayDefine gatewayDefine = new GatewayDefine();                gatewayDefine.setId(r.getId());                gatewayDefine.setUri(r.getUri().toString());                gatewayDefine.setPredicates(JSON.toJSONString(r.getPredicates()));                gatewayDefine.setFilters(JSON.toJSONString(r.getFilters()));                gatewayDefineService.save(gatewayDefine);                return Mono.empty();             } catch (Exception e) {                e.printStackTrace();                return Mono.defer(() - Mono.error(new NotFoundException("RouteDefinition save error: "+ r.getId())));            }         });    }     @Override    public MonoVoid delete(MonoString routeId) {        return routeId.flatMap(id - {            try {                gatewayDefineService.deleteById(id);                return Mono.empty();             } catch (Exception e) {                e.printStackTrace();                return Mono.defer(() - Mono.error(new NotFoundException("RouteDefinition delete error: " + routeId)));            }                    });    }}

    public class MysqlRouteDefinitionRepository implements RouteDefinitionRepository {
    @Autowired
    private GatewayDefineService gatewayDefineService;
    @Override
    public FluxRouteDefinition getRouteDefinitions() {
    try {
    ListGatewayDefine gatewayDefineList = gatewayDefineService.findAll();
    MapString, RouteDefinition routes = new LinkedHashMapString, RouteDefinition();
    for (GatewayDefine gatewayDefine: gatewayDefineList) {
    RouteDefinition definition = new RouteDefinition();
    definition.setId(gatewayDefine.getId());
    definition.setUri(new URI(gatewayDefine.getUri()));
    ListPredicateDefinition predicateDefinitions = gatewayDefine.getPredicateDefinition();
    if (predicateDefinitions != null) {
    definition.setPredicates(predicateDefinitions);
    }
    ListFilterDefinition filterDefinitions = gatewayDefine.getFilterDefinition();
    if (filterDefinitions != null) {
    definition.setFilters(filterDefinitions);
    }
    routes.put(definition.getId(), definition);

    
            }
            return Flux.fromIterable(routes.values());
        } catch (Exception e) {
            e.printStackTrace();
            return Flux.empty();
        }
    }
    
    @Override
    public MonoVoid save(MonoRouteDefinition route) {
        return route.flatMap(r - {
            try {
                GatewayDefine gatewayDefine = new GatewayDefine();
                gatewayDefine.setId(r.getId());
                gatewayDefine.setUri(r.getUri().toString());
                gatewayDefine.setPredicates(JSON.toJSONString(r.getPredicates()));
                gatewayDefine.setFilters(JSON.toJSONString(r.getFilters()));
                gatewayDefineService.save(gatewayDefine);
                return Mono.empty();
    
            } catch (Exception e) {
                e.printStackTrace();
                return Mono.defer(() - Mono.error(new NotFoundException("RouteDefinition save error: "+ r.getId())));
            }
    
        });
    }
    
    @Override
    public MonoVoid delete(MonoString routeId) {
        return routeId.flatMap(id - {
            try {
                gatewayDefineService.deleteById(id);
                return Mono.empty();
    
            } catch (Exception e) {
                e.printStackTrace();
                return Mono.defer(() - Mono.error(new NotFoundException("RouteDefinition delete error: " + routeId)));
            }            
        });
    }
    

    }

    在启动类GatewayServiceApplication中添加两个Bean。

    123456789
    @Bean    public RouteDefinitionWriter routeDefinitionWriter() {        return new InMemoryRouteDefinitionRepository();    }     @Bean    public MysqlRouteDefinitionRepository mysqlRouteDefinitionRepository() {        return new MysqlRouteDefinitionRepository();    }

    @Bean
    public RouteDefinitionWriter routeDefinitionWriter() {
    return new InMemoryRouteDefinitionRepository();
    }

    
    @Bean
    public MysqlRouteDefinitionRepository mysqlRouteDefinitionRepository() {
        return new MysqlRouteDefinitionRepository();
    }
    

    添加ApplicationStartup类,在Spring Boot启动时装载路由配置信息, 说明看注释:

    123456789101112131415
    /** * 在Spring Boot程序启动后会检测程序中是否有CommandLineRunner * 和ApplicationRunner接口的实例, * 如果存在,则会执行对应实现类中的run()方法,而且只执行一次 */public class ApplicationStartup implements ApplicationRunner {     @Autowired    private GatewayDefineService gatewayDefineService;     @Override    public void run(ApplicationArguments args) throws Exception {        gatewayDefineService.loadRouteDefinition();    }}

    /**

    • 在Spring Boot程序启动后会检测程序中是否有CommandLineRunner
    • 和ApplicationRunner接口的实例,
    • 如果存在,则会执行对应实现类中的run()方法,而且只执行一次

    */
    public class ApplicationStartup implements ApplicationRunner {

    
    @Autowired
    private GatewayDefineService gatewayDefineService;
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
        gatewayDefineService.loadRouteDefinition();
    }
    

    }

    完成。

    其他

    我这里面没有界面设置路由,我是在配置文件中配置我要的路由,然后通过 /actuator/gateway/routes 获取所有路由的json, 也可以通过 /actuator/gateway/routes/{id} 获取单独一个路由的json.然后手工往数据库里面插入数据,再把网关服务停止,删除配置文件中的路由设定,再重新启动网关功能,通过 /actuator/gateway/routes 能够获取同样的路由json, 通过curl访问设置的路由同样生效。

    当然完全可以独立开发一个应用,有界面来读取数据库中的路由配置,可以增加和修改路由信息。再通过Spring Cloud Config的配置来刷新多个网关路由的信息,实现多个网关服务的路由信息实时更新。反正有各种方法可供选择。

    最后

    完全是记录自己前一段时间的研究心得。水平有限,有什么不对的地方请大家指正。还有,Spring Cloud GateWay还不支持OAuth2, 所以想统一集成授权、认证等功能的还是使用ZUUL吧。下一次有时间,我会写一下ZUUL的动态路由功能实现以及避免频繁刷新路由信息。反正和GateWay相似,但是还是有区别的。

    实现参考了网上很多人的源码和文章,在此表示感谢!也阅读了Spring Cloud GateWay 部分源代码,对Spring Cloud GateWay有了一定的认识,聊以自慰。

    后记

    最新的Spring Cloud Greenwich.RELEASE中Gateway 过滤器新增支持OAuth2,我觉得可以抛弃ZUUL了。

    更多Java技术文章,尽在【Java知音】网站。

    网址:www.javazhiyin.com  ,搜索Java知音可达!

    本文作者:【Java知音】辣椒

     

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 【加精】Spring Cloud GateWay动态路由配置


 上一篇
从 Spring Cloud 看一个微服务框架的「五脏六腑」 从 Spring Cloud 看一个微服务框架的「五脏六腑」
作者:福笑链接:webfe.kujiale.com/spring-could-heart/ 链接:webfe.kujiale.com/spring-could-heart/ Spring Clou
下一篇 
【加精】Spring Cloud Zuul路由动态配置 【加精】Spring Cloud Zuul路由动态配置
Spring Cloud Zuul动态路由配置 Zuul配置 在mysql中创建路由信息表,对于类如下: 定义CustomRouteLocator类 增加CustomZuulConfig类,主要是为了配置CustomRouteLocator