• Springcloud Gateway 路由管理


    Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

    相应的入门demo网上很多,我们这边一笔带过


    1.引入pom依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    2.application.yml 配置文件 

    server:
      servlet:
        context-path: /
      port: 18889
    spring:
      application:
        name: client-gateway
      cloud:
        gateway:
          discovery:
            locator:
              enabled: false #表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,# 这个router将以服务名开头的请求路径转发到对应的服务
              lower-case-service-id: true #将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了,比如以/service-hi/*的请求路径被路由转发到服务名为service-hi的服务上
          routes:
          - id: test-id
            uri: lb://client-manage
            order: -1
            predicates:
            - Path=/api2/**
            filters:
            - StripPrefix=1
    • id:我们自定义的路由 ID,保持唯一
    • uri:目标服务地址
    • predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
    • filters:过滤规则,

     上面那段路由配置表示所有已包含 /api2/ 的url都会被路由到 client-manage服务,StripPrefix=1表示路由时会去除/api2/。

    我们也可以使用api的方式配置

    @Bean
        public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
            // @formatter:off
            return builder.routes ()
                    .route (r -> r.path ("/api2/**")
                            .filters (f -> f.stripPrefix (1))
                            .uri ("lb://client-manage")
                            .order (0)
                            .id ("client-manage")
                    )
                    .build ();
        }

    这两种配置效果是一致。

    路由扩展

    基于上面两种配置,我们的网关已经具有了路由的功能了。但是这里还不够工程化。设想下,现在我有上百个路由信息,配置文件或者api的形式去配置必然会导致可读性的缺失。同时我还想实现不停机的增加路由。这里就引入了动态增加路由的概念。翻看Gateway的代码,发现Gateway代码本身就支持动态增加路由,相关代码在 org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint#save

    入参的 RouteDefinition 是对路由的封装,与上文的配置文件一一相对应。最终实际调用的是  RouteDefinitionWriter的save方法。

    基于上述代码,我们要做以下几件事情

    1.定义外部存储文件中的RouteDefinition的数据结构

    2.启动时自动读取路由信息写入内存中。


    单个路由的添加

    @PostMapping(value = "addRoute")
        @ResponseBody
        public String  addRoute() throws URISyntaxException{
    
                RouteDefinition routeDefinition=new RouteDefinition();
                routeDefinition.setId("test-id");
                List<PredicateDefinition> predicates=new ArrayList<>();
                PredicateDefinition definition=new PredicateDefinition();
                //注意name
                definition.setName("Path");
                definition.addArg("pattern","/api2/**");
                predicates.add(definition);
                routeDefinition.setPredicates(predicates);
    
                List<FilterDefinition> filters =new ArrayList<>();
                FilterDefinition filterDefinition = new FilterDefinition();
                //注意name
                filterDefinition.setName("StripPrefix");
                filterDefinition.addArg("parts","1");
                filters.add(filterDefinition);
                routeDefinition.setFilters(filters);
    
                URI uri = new URI("lb://client-manage");
                routeDefinition.setUri(uri);
                routeDefinition.setOrder(0);
                String save = routeService.add(routeDefinition);
                System.out.println(save);
                return "";
        }

    为确保功能的实现,我们先写死一个配置尝试用这种方式配置路由。这里注意两点

    PredicateDefinition配置

    PredicateDefinition的name代表每一个工厂类,只能从以下选择

     addArgs的key是相应方法参数名称

    public GatewayFilterSpec stripPrefix(int parts) {
            return filter(getBean(StripPrefixGatewayFilterFactory.class)
                    .apply(c -> c.setParts(parts)));
        }

     FilterDefinition配置

    FilterDefinition和PredicateDefinition类似,name从以下选择

    配置文件读取

    单个配置生效后我们开始外部文件的形式去配置,这里为了便捷我依然从项目配置文件读取。实际我们可以把配置放到数据库,缓存。

    新建api.properties文件

    新增配置

    api.methods.api2={"predicateDefinition":[{"predicateValue":"/api2/**","name":"Path","predicateKey":"pattern"}],"id":"test_id","uri":"lb://client-manage","filterDefinition":[{"filterKey":"parts","filterValue":"1","name":"StripPrefix"}],"order":"0"}

     定义RouteDefines读取配置文件

    @Configuration
    @PropertySource("classpath:api.properties")
    @ConfigurationProperties(prefix = "api")
    public class RouteDefines {
    
      public Map<String, String> methods = new HashMap<>();
    
      public Map<String, String> getMethods() {
        return methods;
      }
    
      public void setMethods(Map<String, String> methods) {
        this.methods = methods;
      }
    
    }

    定义InitRouteApplication初始化路由信息写入内存

    package com.hdkj.client.gateway;
    
    import com.alibaba.fastjson.JSONArray;
    import com.alibaba.fastjson.JSONObject;
    import com.hdkj.client.gateway.configuration.RouteDefines;
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.ApplicationArguments;
    import org.springframework.boot.ApplicationRunner;
    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.stereotype.Component;
    
    /**
     * @author Xu.Minzhe
     * @version V1.0
     * @package com.hdkj.client.gateway
     * @class: InitRouteApplication.java
     * @description: 初始化路由信息
     * @Date 2019-04-25 10:15
     */
    @Component
    public class InitRouteApplication implements ApplicationRunner {
            private static final Logger logger = LoggerFactory.getLogger(InitRouteApplication.class);
    
            @Autowired
            private RouteDefines routeDefines;
    
            @Autowired
            private DynamicRouteService routeService;
    
            @Override
            public void run(ApplicationArguments args) throws Exception {
                    Map<String, String> methods = routeDefines.getMethods();
                    methods.values().stream().forEach(x->{
                            try {
                                    System.out.println("配置文件读取的信息"+x);
                                    JSONObject jsonObject = JSONObject.parseObject(x);
                                    //组装RouteDefinition
                                    RouteDefinition routeDefinition = getRouteDefinition(jsonObject);
                                    //路由信息写入
                                    String save = routeService.add(routeDefinition);
                            } catch (Exception e) {
                                    logger.error("[路由初始化] 异常",e);
                            }
                    });
            }
    
            /**
             * 组装RouteDefinition
             * @param jsonObject
             * @return
             * @throws URISyntaxException
             */
            private RouteDefinition getRouteDefinition(JSONObject jsonObject) throws URISyntaxException {
                    RouteDefinition routeDefinition=new RouteDefinition();
                    routeDefinition.setId(jsonObject.getString("id"));
                    List<PredicateDefinition> predicateList = getPredicateList(jsonObject);
                    routeDefinition.setPredicates(predicateList);
    
                    List<FilterDefinition> filterDefinition1 = getFilterDefinition(jsonObject);
                    routeDefinition.setFilters(filterDefinition1);
    
                    URI uri = new URI(jsonObject.getString("uri"));
                    routeDefinition.setUri(uri);
                    routeDefinition.setOrder(jsonObject.getIntValue("order"));
                    return routeDefinition;
            }
    
            /**
             * 解析json 获得PredicateList
             * @param jsonObject
             * @return
             */
            private List<PredicateDefinition> getPredicateList(JSONObject jsonObject) {
                    JSONArray predicateDefinition = jsonObject.getJSONArray("predicateDefinition");
                    List<PredicateDefinition> predicates=new ArrayList<>();
                    predicateDefinition.stream().forEach(predicate->{
                            JSONObject jsonObject1 = JSONObject.parseObject(predicate.toString());
                            PredicateDefinition definition=new PredicateDefinition();
                            definition.setName(jsonObject1.getString("name"));
                            definition.addArg(jsonObject1.getString("predicateKey"),jsonObject1.getString("predicateValue"));
                            predicates.add(definition);
    
                    });
                    return predicates;
            }
    
            /**
             * 解析json 获得FilterDefinitionList
             * @param jsonObject
             * @return
             */
            private List<FilterDefinition> getFilterDefinition(JSONObject jsonObject) {
                    JSONArray predicateDefinition = jsonObject.getJSONArray("filterDefinition");
                    List<FilterDefinition> predicates=new ArrayList<>();
                    predicateDefinition.stream().forEach(predicate->{
                            JSONObject jsonObject1 = JSONObject.parseObject(predicate.toString());
                            FilterDefinition definition=new FilterDefinition();
                            definition.setName(jsonObject1.getString("name"));
                            definition.addArg(jsonObject1.getString("filterKey"),jsonObject1.getString("filterValue"));
                            predicates.add(definition);
    
                    });
                    return predicates;
            }
    }

    这里DynamicRouteService是路由写入实现类

    @Component
    public class DynamicRouteService implements ApplicationEventPublisherAware {
            @Autowired
            private RouteDefinitionWriter routeDefinitionWriter;
    
            private ApplicationEventPublisher publisher;
    
    
            /**
             * 增加路由
             * @param definition
             * @return
             */
            public String add(RouteDefinition definition) {
                    routeDefinitionWriter.save(Mono.just(definition)).subscribe();
                    this.publisher.publishEvent(new RefreshRoutesEvent(this));
                    return "success";
            }
    
            @Override
            public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
                    this.publisher = applicationEventPublisher;
    
            }
    }

    以上便是路由加载的实现。至于动态新增路由,有了以上的代码,实现也是相当简单了。这里不再叙述。

  • 相关阅读:
    如何用Spring Security OAuth2 实现登录互踢,面试必学
    你确定Java注释不会被执行吗?80%的人都不知道
    常见Java性能问题一招帮你解决,80%的人都不知道
    Spring中的反射与反射的原理,案例详解
    10大Java方向最热门的开源项目,高薪必学
    2020最新Python学习常见十大问题总结
    Java源码系列-LinkedHashMap,高薪面试必学
    Java8的Lambda表达式你了解吗?80%的都不知道
    分块矩阵
    矩阵--逆矩阵和伴随矩阵
  • 原文地址:https://www.cnblogs.com/xmzJava/p/10767365.html
Copyright © 2020-2023  润新知