Gateway 网关 之 自定义路由加载(源码分析)

2021/8/17 1:06:12

本文主要是介绍Gateway 网关 之 自定义路由加载(源码分析),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

源码梳理:

RouteDefinitionLocator 是路由定义定位器的顶级接口,它的主要作用就是读取路由的配置信息(org.springframework.cloud.gateway.route.RouteDefinition)。它有五种不同的实现类,如图:


RouteDefinitionLocator

类 : org.springframework.cloud.gateway.route.RouteDefinitionLocator;路由定义定位器接口,只有一个方法,用来获取路由定义列表的方法。

public interface RouteDefinitionLocator {
 
   Flux<RouteDefinition> getRouteDefinitions();
}

通过 RouteDefinitionLocator 的类图,可以看出该接口有多个实现类:

1、PropertiesRouteDefinitionLocator:基于属性配置

2、DiscoveryClientRouteDefinitionLocator:基于服务发现

3、CompositeRouteDefinitionLocator:组合方式

4、CachingRouteDefinitionLocator:缓存方式

5、其中还有一个接口 RouteDefinitionRepository 继承自RouteDefinitionLocator,用于对路由定义的操作(保存、删除路由定义)

 


RouteDefinition

RouteDefinition 作为GatewayProperties中的属性,在网关启动的时候读取配置文件中的相关配置信息,源码  其属性如下:

@Validated
public class RouteDefinition {
 
    /**
     * 路由唯一ID ,默认为 UUID
     */
    private String id;
 
    /**
     * 路由断言定义列表
     */
    @NotEmpty
    @Valid
    private List<PredicateDefinition> predicates = new ArrayList();
    /**
     * 过滤器定义列表
     */
    @Valid
    private List<FilterDefinition> filters = new ArrayList();
    /**
     * 转发地址
     */
    @NotNull
    private URI uri;
 
    /**
     * 
     */
    private Map<String, Object> metadata = new HashMap();
 
    /**
     * 优先级
     */
    private int order = 0;
 
    public RouteDefinition() {
    }
 
    public RouteDefinition(String text) {
        int eqIdx = text.indexOf("=");
        if (eqIdx <= 0) {
            throw new ValidationException("Unable to parse RouteDefinition text '" + text + "'" +
                    ", must be of the form name=value");
        }
 
        setId(text.substring(0, eqIdx));
 
        String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ",");
 
        setUri(URI.create(args[0]));
 
        for (int i = 1; i < args.length; i++) {
            this.predicates.add(new PredicateDefinition(args[i]));
        }
    }
}

进入断言和路由器属性可以看到他们 是一个 Map 数据结构,可以存放多个对应的 键值对数组

 


RouteDefinitionRepository & InMemoryRouteDefinitionRepository

RouteDefinitionRepository 接口中的方法用来对 RouteDefinition 进行增、删、查操作

  • RouteDefinitionLocator 定义了用来获取路由定义列表的方法:
public interface RouteDefinitionLocator {
 
   Flux<RouteDefinition> getRouteDefinitions();
}
  • RouteDefinitionWriter 定义了 写和删除的方法
public interface RouteDefinitionWriter {
 
   Mono<Void> save(Mono<RouteDefinition> route);
 
   Mono<Void> delete(Mono<String> routeId);
} 

 

RouteDefinitionRepository 接口负责将两个以上两个接口方法汇集:

public interface RouteDefinitionRepository extends RouteDefinitionLocator, RouteDefinitionWriter {
}

 

InMemoryRouteDefinitionRepository 实现了 RouteDefinitionRepository 接口,基于内存的路由定义仓库,将 路由信息存储再 内存中的 Map,提供服务的:读、写、删除

public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
 
    /**
     * 用来存储路由信息的 Map
     */
    private final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap());
 
    /**
     * 保存路由定义到内存中
     *
     * @param route
     * @return
     */
    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap((r) -> {
            this.routes.put(r.getId(), r);
            return Mono.empty();
        });
    }
 
    /**
     * 根据路由id从内存中删除指定路由定义
     *
     * @param routeId
     * @return
     */
    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap((id) -> {
            if (this.routes.containsKey(id)) {
                this.routes.remove(id);
                return Mono.empty();
            } else {
                return Mono.defer(() -> {
                    return Mono.error(new NotFoundException("RouteDefinition not found: " + routeId));
                });
            }
        });
    }
 
    /**
     * 获取内存中路由定义列表
     *
     * @return
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(this.routes.values());
    }
}

 

PropertiesRouteDefinitionLocator 基于配置属性的路由定义定位器

从配置文件 yaml或properties中读取路由配置信息,如代码所示:

public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {
 
   private final GatewayProperties properties;
 
   public PropertiesRouteDefinitionLocator(GatewayProperties properties) {
      this.properties = properties;
   }
 
   @Override
   public Flux<RouteDefinition> getRouteDefinitions() {
      return Flux.fromIterable(this.properties.getRoutes());
   }
}

DiscoveryClientRouteDefinitionLocator 基于服务发现的路由定义定位器

该类通过服务发现组件从注册中心获取服务信息,此时路由定义的源就是配置中心

public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
    // 服务发现客户端
    private final DiscoveryClient discoveryClient;
    // 服务发现属性
    private final DiscoveryLocatorProperties properties;
    // 路由id前缀
    private final String routeIdPrefix;
 
    // ------------------------------省略--------------------------------
}
 
/**
 * 服务发现属性对象
 */
@ConfigurationProperties("spring.cloud.gateway.discovery.locator")
public class DiscoveryLocatorProperties {
    // 开启服务发现
    private boolean enabled = false;
    // 路由前缀,默认为 discoveryClient. getClass(). getSimpleName() + "_". 
    private String routeIdPrefix;
    // SpEL 表达式,判断网关是否集成一个服务,默认为 true 
    private String includeExpression = "true";
    // SpEL 表达式,为每个路由创建uri,默认为'lb://'+ serviceId 
    private String urlExpression = "'lb://'+ serviceId";
    // 在 断言 和 过滤器 中使用小写 serviceId,默认为 false
    private boolean lowerCaseServiceId = false;
    // 路由断言定义列表
    private List<PredicateDefinition> predicates = new ArrayList();
    //过滤器定义列表
    private List<FilterDefinition> filters = new ArrayList();
    // ------------------------------省略--------------------------------
}

在 DiscoveryLocatorProperties 定义了以上属性,要启用基于服务发现的路由定义定位器就必须设置

spring.cloud.gateway.discovery.locator.enabled= true

 


实操:(自定义路信息存储管理)

1、自定义一个路由信息描述类 存储路由信息的对象,继承 RouteDefinition  根据自己的需求可自行扩展增加属性字段(使用数据库对应字段,可自行灵活的扩展属性字段),数据库中存储此 类 对象

/**
 * 扩展此类支持序列化路由,(可将路由信息添加到数据库及Redis)
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class RouteDefinitionInDb extends RouteDefinition implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 路由名称
     */
    private String routeName;
}

2、路由数据的:增、删、读 的类(这里初始化加载路由信息也放在同一个类中处理,实际开发中可自行拆解,这里的示例 代码并没有处理 路由存储 MySQL 和 Redis ,读者请自行实现)

/**
 * 自定义路由系列化加载配置,负责对路由信息的读、写、删除的方法
 */
@Slf4j
@Component
public class RouteDefinitionWriterAndReader implements RouteDefinitionRepository {
 
    /**
     * 这个是 Gateway 默认加载路由信息的配置(示例代码并没有使用Redis 和 MySQL ,读者自行变更为 MySQL 中存储,启动时加载到 Redis 中即可)
     */
    @Autowired
    private GatewayProperties properties;
 
    /**
     * 后续改为 Redis 和 数据库方式存储 路由信息,此处改为相应的 RedisTemplate
     */
    private static volatile Map<String, RouteDefinitionInDb> ROUTES_MAP = new ConcurrentHashMap<>(1 << 4);
 
 
    /**
     * 初始化配置文件中的路由到自定义容器中,后续直接从数据库中存储到Redis 去
     */
    @PostConstruct
    public void initRoute() {
        properties.getRoutes().forEach(route -> {
            RouteDefinitionInDb routeInDb = new RouteDefinitionInDb();
            BeanUtil.copyProperties(route, routeInDb);
            // 暂时没有路由名称,ID代替
            routeInDb.setRouteName("路由名称:" + route.getId());
            log.info("加载路由:{},路由信息为:{}", routeInDb.getRouteName(), JSON.toJSONString(routeInDb));
            ROUTES_MAP.put(route.getId(), routeInDb);
        });
        log.info("路由加载完毕,路由数量为:{}", ROUTES_MAP.size());
    }
 
 
    /**
     * 获取路由配置
     *
     * @return
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        // RouteDefinition 后续可改为数据库存储的对象 RouteDefinitionInDb
        List<RouteDefinitionInDb> routesInDb = ROUTES_MAP.entrySet().stream()
                .map(routeEntry -> routeEntry.getValue())
                .collect(Collectors.toList());
        return Flux.fromIterable(routesInDb);
    }
 
 
    /**
     * 保存路由配置(暂时未实现具体逻辑)
     *
     * @param route
     * @return
     */
    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        // 暂时使用默认方式配置,即使用配置文件读取的方式配置,后续改为Redis存储
        Mono<Void> mono = route.flatMap(r -> {
            RouteDefinitionInDb routeInDb = new RouteDefinitionInDb();
            BeanUtil.copyProperties(r, routeInDb);
            ROUTES_MAP.put(routeInDb.getId(), routeInDb);
            return Mono.empty();
        });
        return mono;
    }
 
    /**
     * 删除路由配置(暂时未实现具体逻辑)
     *
     * @param routeId
     * @return
     */
    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        // 时使用默认方式配置,即使用配置文件读取的方式配置,后续改为Redis存储
        return routeId.flatMap(id -> {
            if (ROUTES_MAP.containsKey(id)) {
                ROUTES_MAP.remove(id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
        });
    }
}

 



这篇关于Gateway 网关 之 自定义路由加载(源码分析)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程