微服务都是互相独立的,假如我们的网关和其他服务都在线上已经运行了好久,这个时候增加了一个微服务,这个时候要通过网关访问的话需要通过修改配置文件来增加路由规则,并且需要重启项目,所以我们需要实现动态路由
新建路由发布接口
/**
* 路由配置服务
* @author : jiagang
* @date : Created in 2022/7/20 11:07
*/
public interface RouteService {
/**
* 更新路由配置
*
* @param routeDefinition
*/
void update(RouteDefinition routeDefinition);
/**
* 添加路由配置
*
* @param routeDefinition
*/
void add(RouteDefinition routeDefinition);
}
实现类如下
package com.mdx.gateway.service.impl;
import com.mdx.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
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.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
/**
* @author : jiagang
* @date : Created in 2022/7/20 11:10
*/
@Service
@Slf4j
public class RouteServiceImpl implements RouteService, ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
/**
* 事件发布者
*/
private ApplicationEventPublisher publisher;
@Override
public void update(RouteDefinition routeDefinition) {
log.info("更新路由配置项:{}", 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) {
log.info("新增路由配置项:{}", 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的父接口之一,他的功能就是发布事件,也就是把某个事件告诉所有与这个事件相关的监听器将路由信息放到nacos的配置文件下
新建配置文件,并将order服务的路由添加到配置文件
配置路由如下:
[
{
"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 : jiagang
* @date : Created in 2022/7/20 14:44
*/
@ConfigurationProperties(prefix = "gateway.routes.config")
@Component
@Data
public class GatewayRouteConfigProperties {
private String dataId;
private String group;
private String namespace;
}
ConfigService 这个类是nacos的分布式配置接口,主要是用来获取配置和添加监听器
由NacosFactory来创建ConfigService
/**
* 将configService交由spring管理
* @author : jiagang
* @date : Created in 2022/7/20 15:27
*/
@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());
return NacosFactory.createConfigService(properties);
}
}
项目启动时会加载这个类
@PostConstruc
注解的作用,在spring bean的生命周期依赖注入完成后被调用的方法
package com.mdx.gateway.route;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mdx.common.utils.StringUtils;
import com.mdx.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* @author : jiagang
* @date : Created in 2022/7/20 15:04
*/
@Component
@Slf4j
@RefreshScope
public class GatewayRouteInitConfig {
@Autowired
private GatewayRouteConfigProperties configProperties;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Autowired
private RouteService routeService;
/**
* nacos 配置服务
*/
@Autowired
private ConfigService configService;
/**
* JSON 转换对象
*/
private final ObjectMapper objectMapper = new ObjectMapper();
@PostConstruct
public void init() {
log.info("开始网关动态路由初始化...");
try {
// getConfigAndSignListener()方法 发起长轮询和对dataId数据变更注册监听的操作
// getConfig 只是发送普通的HTTP请求
// String getConfig(String dataId, String group, long timeoutMs) throws NacosException;
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.isNotEmpty(configInfo)) {
log.info("接收到网关路由更新配置:\r\n{}", configInfo);
List<RouteDefinition> routeDefinitions = null;
try {
routeDefinitions = objectMapper.readValue(configInfo, new TypeReference<List<RouteDefinition>>() {
});
} catch (JsonProcessingException e) {
log.error("解析路由配置出错," + e.getMessage(), e);
}
for (RouteDefinition definition : Objects.requireNonNull(routeDefinitions)) {
routeService.update(definition);
}
} else {
log.warn("当前网关无动态路由相关配置");
}
}
});
log.info("获取网关当前动态路由配置:\r\n{}", initConfigInfo);
if (StringUtils.isNotEmpty(initConfigInfo)) {
List<RouteDefinition> routeDefinitions = objectMapper.readValue(initConfigInfo, new TypeReference<List<RouteDefinition>>() {
});
for (RouteDefinition definition : routeDefinitions) {
routeService.add(definition);
}
} else {
log.warn("当前网关无动态路由相关配置");
}
log.info("结束网关动态路由初始化...");
} catch (Exception e) {
log.error("初始化网关路由时发生错误", e);
}
}
}
如果项目启动时,在发布路由的时候卡在 this.publisher.publishEvent(new RefreshRoutesEvent(this));
这个地方走不下去,请在GatewayRouteInitConfig
这个类加@RefreshScope
注解
前面我们已经把本地的yml中的路由注释掉了,现在我们来通过gateway服务来掉一个order服务的接口
接口地址:http://localhost:9010/mdx-shop-order/order/lb
其中9010是网关端口
可以看到路由成功
然后我们再在nacos配置中心加一个user服务的路由
[
{
"predicates":[
{
"args":{
"pattern":"/mdx-shop-order/**"
},
"name":"Path"
}
],
"id":"mdx-shop-order",
"filters":[
{
"args":{
"parts":1
},
"name":"StripPrefix"
}
],
"uri":"lb://mdx-shop-order",
"order":1
},
{
"predicates":[
{
"args":{
"pattern":"/mdx-shop-user/**"
},
"name":"Path"
}
],
"id":"mdx-shop-user",
"filters":[
{
"args":{
"parts":1
},
"name":"StripPrefix"
}
],
"uri":"lb://mdx-shop-user",
"order":2
}
]
然后点发布
可以看到gateway的监听器已经监听到配置的改动
不重新启动gateway的情况下再来通过网关访问下user服务
接口地址:http://localhost:9010/mdx-shop-user/user/getOrderNo?userId=mdx123456
其中9010是网关端口
可以看到成功路由
到这里gateway的使用和nacos动态路由就结束了~
方式二与方式一实现方式类似,就是nacos 监听方式不一样,简单记录下
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.lang.reflect.Field;
import java.util.Map;
@Service
@Slf4j
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
//增加路由
public String add(RouteDefinition definition) {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
//更新路由
public String update(RouteDefinition definition) {
try {
delete(definition.getId());
} catch (Exception e) {
log.error("删除路由异常", e);
return "update fail,not find route routeId: " + definition.getId();
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception e) {
log.error("更新路由异常", e);
return "update route fail";
}
}
//删除路由
public String delete(String id) {
StringBuilder sb = new StringBuilder();
this.routeDefinitionWriter.delete(Mono.just(id))
.doOnError(e -> {
String s = String.format("删除路由%s失败", id);
log.error(s);
sb.append(s);
}).doOnSuccess(e -> {
String s = String.format("删除路由%s成功", id);
log.error(s);
sb.append(s);
}).subscribe();
return sb.toString();
}
public Map<String, RouteDefinition> list() {
Map<String, RouteDefinition> routes = Maps.newHashMap();
InMemoryRouteDefinitionRepository in = (InMemoryRouteDefinitionRepository) routeDefinitionWriter;
try {
Field f = InMemoryRouteDefinitionRepository.class.getDeclaredField("routes");
f.setAccessible(true);
routes = (Map<String, RouteDefinition>) f.get(in);
} catch (Exception e) {
e.printStackTrace();
}
return routes;
}
}
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.util.List;
import java.util.Map;
@Component
@Slf4j
public class NacosDynamicSupport implements InitializingBean {
@Value("#{'${spring.application.name}'.concat('.route')}")
private String route;
@Autowired
private DynamicRouteServiceImpl dynamicRouteService;
@Autowired
private NacosConfigManager nacosConfigManager;
@Override
public void afterPropertiesSet() throws Exception {
nacosConfigManager.getConfigService().addListener(route, Constants.DEFAULT_GROUP, new AbstractListener() {
@Override
public void receiveConfigInfo(String config) {
log.error("mgateway:{}", config);
if(StringUtils.isBlank(config)) {
return;
}
List<RouteDefinition> routeDefinitions = JSON.parseArray(config, RouteDefinition.class);
Map<String, RouteDefinition> oldRouteMap = dynamicRouteService.list();
Map<String, RouteDefinition> newRouteMap = Maps.newHashMap();
for(RouteDefinition definition: routeDefinitions) {
String id = definition.getId();
newRouteMap.put(id, definition);
if(!oldRouteMap.containsKey(id)) {
dynamicRouteService.add(definition);
} else if(!definition.equals(oldRouteMap.get(id))) {
dynamicRouteService.update(definition);
}
}
oldRouteMap.forEach((k,v) -> {
if(!newRouteMap.containsKey(k)) {
log.error(dynamicRouteService.delete(k));
}
});
}
});
String routeJson = nacosConfigManager.getConfigService().getConfig(route, Constants.DEFAULT_GROUP, 5000);
if(StringUtils.isNotBlank(routeJson)) {
List<RouteDefinition> routeDefinitions = JSON.parseArray(routeJson, RouteDefinition.class);
for (RouteDefinition definition : routeDefinitions) {
this.dynamicRouteService.add(definition);
}
}
}
}
参考:https://blog.csdn.net/qq_38374397/article/details/125874882