网关的路由配置在项目启动时加载,并存到内存中的路由表内(一个Map),不会改变。也不会监听路由变更,因此,需要监听Nacos的配置变更,手动更新路由表。
1.引入依赖
<!--统一配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--加载bootstrap-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
2.编写配置文件
在网关gateway
的resources
目录创建bootstrap.yaml
文件,内容如下:
spring:
application:
name: gateway # 你的服务名字
cloud:
nacos:
server-addr: 192.168.230.131
config:
file-extension: yaml
shared-configs:
- dataId: shared-log.yaml # 共享日志配置
3.编写动态路由文件
将原本yaml中配置的路由信息删除,在nacos中创建一个新的用于动态路由的文件,使用json的原因是,当我们监听到配置变更后,拉取新的配置文件,返回格式是字符串,在java中json格式的好解析成对象
内容如下:
是一个RouteDefinition数组,其中id和uri跟之前的长相类似,但注意路由断言,也是是一个list格式的,里面存的是断言的对象,由名字(规则类型)和参数组成,这里的路径参数,要按照格式写
"args": {"_genkey_0":"请求格式路径1", "_genkey_1":"请求格式路径2",, "_genkey_2":"以此类推"}
[
{
"id": "item",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
}],
"filters": [],
"uri": "lb://item-service"
},
{
"id": "cart",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/carts/**"}
}],
"filters": [],
"uri": "lb://cart-service"
}
]
4.编写配置监听器
nacosConfigManager.getConfigService()
得到的 ConfigService
实例可以用来从 Nacos 服务器获取配置信息
我们需要在启动服务时候拉取路由信息,并在配置变化时候更新信息,所以可以使用
ConfigService.getConfigAndSignListener(配置文件id,group,超时时间,监听器)方法返回字符串格式的配置文件
RouteDefinitionWriter
是一个接口,用于动态地定义和更新路由信息,允许你在运行时添加、删除或修改路由规则,而不需要重启应用程序。
我们获得了配置文件的字符串形式,可以解析成路由对象,并使用RouteDefinitionWriter
对路由信息进行修改
import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import cn.hutool.core.collection.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
@Slf4j
@Component
public class DynamicRouteLoader {
@Autowired
private RouteDefinitionWriter writer; //用来修改路由
@Autowired
private NacosConfigManager nacosConfigManager; //获取与nacos的连接
// 路由配置文件的id和分组
private final String dataId = "gateway-routes.json";
private final String group = "DEFAULT_GROUP";
// 保存更新过的路由id
private final Set<String> routeIds = new HashSet<>();
@PostConstruct //bean被实例化之后,Spring容器会立即调用带有@PostConstruct 注解的方法
public void initRouteConfigListener() throws NacosException {
// 1.注册监听器并首次拉取配置
String configInfo = nacosConfigManager.getConfigService()
.getConfigAndSignListener(dataId, group, 5000, new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
//更新配置
updateConfigInfo(configInfo);
}
});
// 2.首次启动时,更新一次配置
updateConfigInfo(configInfo);
}
private void updateConfigInfo(String configInfo) {
log.debug("监听到路由配置变更,{}", configInfo);
// 1.解析配置信息,转为RouteDefinition
List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
// 2.更新前先清空旧路由
// 2.1.清除旧路由
for (String routeId : routeIds) {
writer.delete(Mono.just(routeId)).subscribe();
}
routeIds.clear();
// 2.2.判断是否有新的路由要更新
if (CollectionUtil.isEmpty(routeDefinitions)) {
// 无新路由配置,直接结束
return;
}
// 3.更新路由
routeDefinitions.forEach(routeDefinition -> {
// 3.1.更新路由
writer.save(Mono.just(routeDefinition)).subscribe();
// 3.2.记录路由id,方便将来删除
routeIds.add(routeDefinition.getId());
});
}
}