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.YAML 配置文件
- 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; } }
以上便是路由加载的实现. 至于动态新增路由, 有了以上的代码, 实现也是相当简单了. 这里不再叙述.
来源: https://www.cnblogs.com/xmzJava/p/10767365.html