Spring Cloud学习笔记—网关Spring Cloud Gateway动态路由实操练习

Spring Cloud学习笔记—网关Spring Cloud Gateway动态路由实操练习


Spring Cloud Gateway的路由规则不管是卸载yml配置文件,还是写代码里,这两种方式都是不支持动态配置的,Spring Cloud Gateway启动时候,就将路由配置和规则加载到内存里,无法做到不重启网关就可以识别yml配置文件和代码配置的变化。下面就详细介绍如何实现Spring Cloud Gateway的动态路由(即不重启网关就能改变路由规则)。

本文练习是在《Spring Cloud学习笔记—Spring Cloud Gateway官网教程实操练习》基础上扩展。

1、在pom.xml中增加spring-boot-starter-actuator依赖(actuator是实现对springboot监控的功能),完整的pom.xml代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.2-SNAPSHOT</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>wongoing</groupId>
	<artifactId>ms</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>Gateway</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>2020.0.0-SNAPSHOT</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-gateway</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</repository>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</pluginRepository>
		<pluginRepository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>
</project>

2、修改resources/application.yml,增加暴露端点的配置。完整内容如下:

server:
  port: 8080                                #指定当前服务的端口号

#暴露端点
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
      
spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true                     #是否适用默认路由(通过gatewayUri:port/服务名/path直接访问服务接口)
          lower-case-service-id: true       #是否忽略服务名大小写
      routes:                               #gateway配置多条路由规则时注意顺序问题
      
      - id: service0                        #路由规则ID在上下文中唯一
        uri: http://httpbin.org:80          #路由目标服务地址
        predicates:                         #路由条件:请求的路径为http://localhost:8080/get则自动转发至目标服务
        - Path=/get
        filters:                            #过滤器链
        - AddRequestHeader=Hello, World     #添加请求头
        
      - id: service1
        uri: http://localhost:8090
        predicates:
        - Path=/test
        filters:
        - AddRequestHeader=Author, CodingPioneer

3、在Gateway工程下创建一个包com.wongoing.route存放动态路由的相关类。
4、在com.wongoing.route包下创建路由断言模型类GatewayPredicateDefinition.java,代码如下:

package com.wongoing.route;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 功能说明:路由断言模型
 * 修改说明:
 * @author zheng
 * @date 2020-12-18 13:26:44
 * @version 0.1
 */
public class GatewayPredicateDefinition {
	//断言对应的Name
	private String name;
	//配置断言的规则
	private Map<String, String> args = new LinkedHashMap<>();
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public Map<String, String> getArgs() {
		return args;
	}
	
	public void setArgs(Map<String, String> args) {
		this.args = args;
	}
}

5、在com.wongoing.route包下创建路由过滤器模型类GatewayFilterDefinition.java,代码如下:

package com.wongoing.route;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 功能说明:路由过滤器模型
 * 修改说明:
 * @author zheng
 * @date 2020-12-20 15:19:08
 * @version 0.1
 */
public class GatewayFilterDefinition {
	//Filter Name
	private String name;
	//对应的路由规则
	private Map<String, String> args = new LinkedHashMap<>();
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public Map<String, String> getArgs() {
		return args;
	}
	
	public void setArgs(Map<String, String> args) {
		this.args = args;
	}
}

6、在com.wongoing.route包下创建路由模型类GatewayRouteDefinition.java,代码如下:

package com.wongoing.route;

import java.util.ArrayList;
import java.util.List;

/**
 * 功能说明:路由模型
 * 修改说明:
 * @author zheng
 * @date 2020-12-18 13:23:25
 * @version 0.1
 */
public class GatewayRouteDefinition {
	//路由的Id
	private String id;
	//路由断言集合配置
	private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
	//路由过滤器集合配置
	private List<GatewayFilterDefinition> filters = new ArrayList<>();
	//路由规则转发的目标uri
	private String uri;
	//路由执行的顺序
	private int order = 0;
	
	public String getId() {
		return id;
	}
	
	public void setId(String id) {
		this.id = id;
	}
	
	public List<GatewayPredicateDefinition> getPredicates() {
		return predicates;
	}
	
	public void setPredicates(List<GatewayPredicateDefinition> predicates) {
		this.predicates = predicates;
	}
	
	public List<GatewayFilterDefinition> getFilters() {
		return filters;
	}
	
	public void setFilters(List<GatewayFilterDefinition> filters) {
		this.filters = filters;
	}
	
	public String getUri() {
		return uri;
	}
	
	public void setUri(String uri) {
		this.uri = uri;
	}
	
	public int getOrder() {
		return order;
	}
	
	public void setOrder(int order) {
		this.order = order;
	}
}

7、创建包com.wongoing.route.service,存放动态路由业务类。
8、在com.wongoing.route.service包下创建动态路由业务实现类DynamicRouteServiceImpl.java,代码如下:

package com.wongoing.route.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import reactor.core.publisher.Mono;

/**
 * 功能说明:动态路由业务服务实现类
 * 修改说明:
 * @author zheng
 * @date 2020-12-18 13:59:09
 * @version 0.1
 */
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
	@Autowired
	private RouteDefinitionWriter routeDefinitionWriter;
	private ApplicationEventPublisher publisher;
	
	
	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
		this.publisher = applicationEventPublisher;
	}
	
	/**
	 * 功能说明:增加路由
	 * 修改说明:
	 * @author zheng
	 * @date 2020-12-18 13:58:58
	 * @param definition
	 * @return
	 */
	public String add(RouteDefinition definition) {
		this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
		this.publisher.publishEvent(new RefreshRoutesEvent(this));
		return "success";
	}

	/**
	 * 功能说明:更新路由
	 * 修改说明:
	 * @author zheng
	 * @date 2020-12-18 13:36:23
	 * @param definition
	 * @return
	 */
	public String update(RouteDefinition definition) {
		try {
			this.delete(definition.getId());
		} catch(Exception ex) {
			return "update route fail, not find route routeId: " + definition.getId();
		}
		try {
			this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
			this.publisher.publishEvent(new RefreshRoutesEvent(this));
			return "success";
		} catch(Exception e) {
			return "update route fail";
		}
	}
	
	/**
	 * 功能说明:删除路由
	 * 修改说明:
	 * @author zheng
	 * @date 2020-12-18 13:37:05
	 * @param id
	 * @return
	 */
	public Mono<ResponseEntity<Object>> delete(String id) {
		return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> { 
			this.publisher.publishEvent(new RefreshRoutesEvent(this));
			ResponseEntity<Object> result = ResponseEntity.ok().build();
			return Mono.just(result);
		})).onErrorResume((t) -> {
			return t instanceof NotFoundException;
		}, (t) -> {
			return Mono.just(ResponseEntity.notFound().build());
		});
	}
}

9、创建包com.wongoing.rute.controller,存放动态路由控制器类。
10、在com.wongoing.rute.controller包下创建控制器类RouteController.java,代码如下:

package com.wongoing.route.controller;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
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.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;

import com.wongoing.route.GatewayFilterDefinition;
import com.wongoing.route.GatewayPredicateDefinition;
import com.wongoing.route.GatewayRouteDefinition;
import com.wongoing.route.service.DynamicRouteServiceImpl;

import reactor.core.publisher.Mono;

/**
 * 功能说明:路由控制器类
 * 修改说明:
 * @author zheng
 * @date 2020-12-18 13:59:31
 * @version 0.1
 */
@RestController
@RequestMapping("/route")
public class RouteController {
	
	@Autowired
	private DynamicRouteServiceImpl dynamicRouteService;
	
	/**
	 * 功能说明:增加路由
	 * 修改说明:
	 * @author zheng
	 * @date 2020-12-18 13:45:33
	 * @param gwdefinition
	 * @return
	 */
	@PostMapping("/add")
	public String add(@RequestBody GatewayRouteDefinition gwdefinition) {
		String flag = "fail";
		try {
			RouteDefinition definition = assembleRouteDefinition(gwdefinition);
			flag = this.dynamicRouteService.add(definition);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return flag;
	}
	
	/**
	 * 功能说明:删除路由
	 * 修改说明:
	 * @author zheng
	 * @date 2020-12-18 14:08:45
	 * @param id
	 * @return
	 */
	@DeleteMapping("/routes/{id}")
	public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
		try {
			System.out.println("This delete id is : " + id);
			return this.dynamicRouteService.delete(id);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 功能说明:更新路由
	 * 修改说明:
	 * @author zheng
	 * @date 2020-12-18 14:11:35
	 * @param gwdefinition
	 * @return
	 */
	@PostMapping("/update")
	public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
		RouteDefinition definition = assembleRouteDefinition(gwdefinition);
		return this.dynamicRouteService.update(definition);
	}
	
	/**
	 * 功能说明:把传递进来的参数转换成路由对象
	 * 修改说明:
	 * @author zheng
	 * @date 2020-12-18 13:47:29
	 * @param gwdefinition
	 * @return
	 */
	private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
		RouteDefinition definition = new RouteDefinition();
		definition.setId(gwdefinition.getId());
		definition.setOrder(gwdefinition.getOrder());
		
		//设置断言
		List<PredicateDefinition> pdList = new ArrayList<>();
		List<GatewayPredicateDefinition> gatewayPredicateDefinitionList = gwdefinition.getPredicates();
		for (GatewayPredicateDefinition gpDefinition : gatewayPredicateDefinitionList) {
			PredicateDefinition predicate = new PredicateDefinition();
			predicate.setArgs(gpDefinition.getArgs());
			predicate.setName(gpDefinition.getName());
			pdList.add(predicate);
		}
		definition.setPredicates(pdList);
		
		//设置过滤器
		List<FilterDefinition> filters = new ArrayList<>();
		List<GatewayFilterDefinition> gatewayFilters = gwdefinition.getFilters();
		for(GatewayFilterDefinition filterDefinition : gatewayFilters) {
			FilterDefinition filter = new FilterDefinition();
			filter.setName(filterDefinition.getName());
			filter.setArgs(filterDefinition.getArgs());
			filters.add(filter);
		}
		definition.setFilters(filters);
		
		URI uri = null;
		if (gwdefinition.getUri().startsWith("http")) {
			uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
		} else { 
			//uri为 lb://consumer-service 时使用下面的方法
			uri = URI.create(gwdefinition.getUri());
		}
		definition.setUri(uri);
		return definition;
	}
}

11、启动Gateway项目,使用Postman进行测试。
12、通过actuator查看已有的路由配置。
在Postman中建立一个GET请求,地址是:http://localhost:8080/actuator/gateway/routes,执行结果如下图:
在这里插入图片描述
这里列出的是resources/application.yml中配置的路由规则。
13、测试新增一个路由。
在Postman中新增一个POST请求,请求地址是:http://localhost:8080/route/update,在请求的Body选项中选择raw,数据类别选择JSON,然后在请求的内容部分输入以下内容:

{
    "id":"service3",
    "uri":"http://www.coding123.cn:80",
    "order":1,
    "predicates":[{"name":"Path", "args":{"pattern":"/demo/threejs/threejs_demo1.html"}}],
    "filters":[{"name":"AddRequestHeader", "args":{"_genkey_0":"test", "_genkey_1":"testValue"}}]
}

执行结果,如下图:
在这里插入图片描述
14、重新执行第12步,看看执行结果中有没有新增路由,执行结果如下图:
在这里插入图片描述
当然也可以通过在浏览器地址栏中输入:http://localhost:8080/demo/threejs/threejs_demo1.html测试路由是否生效,我测试是通过的。
15、删除刚刚添加的那个路由规则。
在Postman中新增一个DELETE请求,在地址栏中输入:http://localhost:8080/route/routes/service3,执行结果如下图:
在这里插入图片描述
16、重新执行第12步验证相应的路由规则是否已删除,执行结果如下图:
在这里插入图片描述
在浏览器地址栏中输入http://localhost:8080/demo/threejs/threejs_demo1.html发现已无法正常显示。
17、总结
通过上面的过程我们已经实现了动态增删路由规则的功能。也有一些注意点:

  • application.yml中配置的路由和初始化代码中编写的路由是不能删除和修改的。
  • 动态增删路由是操作的org.springframework.cloud.gateway.route.RouteDefinitionRepository中的路由信息。
  • application.yml中配置的路由和初始化代码中的路由是在org.springframework.cloud.gateway.route.RouteDefinitionLocator中的。
  • 而/actuator/gateway/routes中监控的是所有的路由信息。
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页