在使用 Cloud Gateway 的时候,官方文档提供的方案总是基于配置文件配置的方式
@SpringBootApplication
public class DemogatewayApplication {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/get")
.uri("http://httpbin.org"))
.route("host_route", r -> r.host("*.myhost.org")
.uri("http://httpbin.org"))
.route("rewrite_route", r -> r.host("*.rewrite.org")
.filters(f -> f.rewritePath("/foo/(?.*)" , "/${segment}"))
.uri("http://httpbin.org"))
.route("hystrix_route", r -> r.host("*.hystrix.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
.uri("http://httpbin.org"))
.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.route("limit_route", r -> r
.host("*.limited.org").and().path("/anything/**")
.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
.uri("http://httpbin.org"))
.build();
}
}
spring:
jmx:
enabled: false
cloud:
gateway:
default-filters:
- PrefixPath=/httpbin
- AddResponseHeader=X-Response-Default-Foo, Default-Bar
routes:
# =====================================
# to run server
# $ wscat --listen 9000
# to run client
# $ wscat --connect ws://localhost:8080/echo
- id: websocket_test
uri: ws://localhost:9000
order: 9000
predicates:
- Path=/echo
# =====================================
- id: default_path_to_httpbin
uri: ${test.uri}
order: 10000
predicates:
- Path=/**
Spring Cloud Gateway作为微服务的入口,需要尽量避免重启,而现在配置更改需要重启服务不能满足实际生产过程中的动态刷新、实时变更的业务需求,所以我们需要在Spring Cloud Gateway运行时动态配置网关。
我们明确了目标需要实现动态路由,那么实现动态路由的方案有很多种,这里拿三种常见的方案来说明下:
前两种方案本质上是一种方案,只是数据存储方式不同,大体实现思路是这样,我们通过接口定义路由的增上改查接口,通过接口来修改路由信息,将修改后的数据存储到mysql或redis中,并刷新路由,达到动态更新的目的。
第三种方案相对前两种相对简单,我们使用nacos的配置中心,将路由配置放在nacos上,写个监听器监听nacos上配置的变化,将变化后的配置更新到GateWay应用的进程内。
我们下面采用第三种方案,因为网关未连接mysql,使用redis还有开发相应的api和对应的web,来配置路由信息,而我们目前没有开发web的需求,所以我们采用第三种方案。
架构图如下:
代码结构如下:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<spring-alibaba-cloud.version>2.2.5.RELEASE</spring-alibaba-cloud.version>
</properties>
<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>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-alibaba-cloud.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!--nacosconfig-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!--nacosdiscovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
/**
* @author code
* @version 1.0
* @Date 2022/9/15 16:39
* @Description ${网关}
*/
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayServerApp {
public static void main( String[] args ) {
//设置全局变量加密密钥
System.setProperty("spring.cloud.nacos.username","nacos");
System.setProperty("spring.cloud.nacos.password","nacos");
SpringApplication.run(GatewayServerApp.class,args);
}
}
server:
port: 7086
spring:
application:
#应用名
name:
cloud:
nacos:
config:
#nacos地址
server-addr:
name: ${spring.application.name}
#命名空间的id
namespace:
#配置文件的类型
file-extension: yml
#配置分组
group:
discovery:
#nacos地址
server-addr:
#命名空间的id
namespace:
group:
gateway:
discovery:
locator:
enabled: true
# routes:
# - id:
# uri: http://ip:port lb:instance
# predicates:
# - Path=/oauth1/**
大致意思是在springboot整合gateway时, gateway组件中的 【spring-boot-starter-webflux】 和 springboot作为web项目启动必不可少的 【spring-boot-starter-web】 出现冲突
我们按照提示: Please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency.
在配置文件配置下 spring.main.web-application-type=reactive 就好了
/**
* @author code
* @version 1.0
* @Date 2022/9/15 17:17
* @Description ${DESCRIPTION}
*/
public interface RouteService {
void update(RouteDefinition routeDefinition);
void add(RouteDefinition routeDefinition);
}
/**
* @author code
* @version 1.0
* @Date 2022/9/15 17:19
* @Description ${DESCRIPTION}
*/
@Service
public class RouteServiceImpl implements RouteService,ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private ApplicationEventPublisher publisher;
@Override
public void update(RouteDefinition routeDefinition){
this.routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
@Override
public void add(RouteDefinition routeDefinition){
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher){
this.publisher = applicationEventPublisher;
}
}
其中:
RouteDefinitionWriter:提供了对路由的增加删除等操作
ApplicationEventPublisher: 是ApplicationContext的父接口之一,他的功能就是发布事件,也就是把某个事件告诉所有与这个事件相关的监听器
配置的路由如下:
[
{
"predicates":[
{
"args":{
"pattern":"/order/**"
},
"name":"Path"
}
],
"id":"mdx-shop-order",
"filters":[
{
"args":{
"parts":1
},
"name":"StripPrefix"
}
],
"uri":"lb://mdx-shop-order",
"order":1
}
]
这个路由配置对应的就是gateway中的RouteDefinition类
gateway:
routes:
config:
data-id: gateway-routes #动态路由
group: shop
namespace: mdx
格式类似于如下:
server:
port: 9010
spring:
application:
name: mdx-shop-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: mdx
group: mdx
gateway:
discovery:
locator:
enabled: true #开启通过服务中心的自动根据 serviceId 创建路由的功能
main:
web-application-type: reactive
gateway:
routes:
config:
data-id: gateway-routes #动态路由
group: shop
namespace: mdx
/**
* @author code
* @version 1.0
* @Date 2022/9/15 16:49
* @Description ${配置类}
*/
@ConfigurationProperties(prefix = "gateway.routes.config")
@Component
@Data
public class GatewayRouteConfigProperties {
private String dataId;
private String group;
private String namespace;
}
ConfigService: 这个类是nacos的分布式配置接口,主要是用来获取配置和添加监听器
由NacosFactory来创建ConfigService
/**
* @author code
* @version 1.0
* @Date 2022/9/15 17:04
* @Description ${configService}
*/
@Configuration
public class GatewayConfigServiceConfig {
@Autowired
private GatewayRouteConfigProperties configProperties;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Bean
public ConfigService configService() throws NacosException{
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR,nacosConfigProperties.getServerAddr());
properties.setProperty(PropertyKeyConst.NAMESPACE,configProperties.getNamespace());
properties.setProperty(PropertyKeyConst.USERNAME,"nacos");
properties.setProperty(PropertyKeyConst.PASSWORD,"nacos");
return NacosFactory.createConfigService(properties);
}
}
项目启动时会加载这个类
@PostConstruc 注解的作用,在spring bean的生命周期依赖注入完成后被调用的方法
/**
* @author code
* @version 1.0
* @Date 2022/9/15 17:13
* @Description ${动态路由}
*/
@Component
@RefreshScope
public class GatewayRouteInitConfig {
@Autowired
private GatewayRouteConfigProperties configProperties;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Autowired
private RouteService routeService;
@Autowired
private ConfigService configService;
private final ObjectMapper objectMapper = new ObjectMapper();
@PostConstruct
public void init(){
try{
String initConfigInfo = configService.getConfigAndSignListener(configProperties.getDataId(), configProperties.getGroup(), nacosConfigProperties.getTimeout(), new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
if(!StringUtils.isEmpty(configInfo)){
List<RouteDefinition> routeDefinitions = null;
try{
routeDefinitions = objectMapper.readValue(configInfo, new TypeReference<List<RouteDefinition>>() {
});
}catch (JsonProcessingException e){
e.printStackTrace();
}
for(RouteDefinition definition : Objects.requireNonNull(routeDefinitions)){
routeService.update(definition);
}
}else {
System.out.println("当前网关无动态路由配置");
}
}
});
if(!StringUtils.isEmpty(initConfigInfo)){
List<RouteDefinition> routeDefinitions = objectMapper.readValue(initConfigInfo, new TypeReference<List<RouteDefinition>>() {
});
for(RouteDefinition definition : Objects.requireNonNull(routeDefinitions)){
routeService.add(definition);
}
}else {
System.out.println("当前网关无动态路由配置");
}
}catch (Exception e){
e.printStackTrace();
}
}
}
注意:要加==@RefreshScope==注解,实时更新路由配置
我们按照提示: Please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency.
在配置文件配置下 spring.main.web-application-type=reactive 就好了
因为gateway是基于webflux
应用名不支持下划线
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
java.util.ConcurrentModificationExceptioni ConcurrentModificationException null