1: Configure related nacos information
Other information has been omitted
Gateway.yml in the project
nacos:
server-addr:xx.xx.xx.xx:8848
namespace: dev
#省略其他代码。。。。。。。。。
#动态路由相关配置
dynamic:
route:
data-id: routes-${spring.application.name}.yaml
group: ROUTE_GROUP
server-addr: xx.xx.xx.xx:8848
namespace: dev
2: Create related entity classes
filter
package com.carry.www.entity;
/**
* 过滤器实体类
*
*/
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
@Data
public class FilterEntity {
// 过滤器对应的Name
private String name;
// 路由规则
private Map<String, String> args = new LinkedHashMap<>();
}
Affirmation:
package com.carry.www.entity;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 路由断言实体类
*
*/
@Data
public class PredicateEntity {
// 断言对应的Name
private String name;
// 断言规则
private Map<String, String> args = new LinkedHashMap<>();
}
Routing class
package com.carry.www.entity;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 路由实体类
*
*/
@Data
public class RouteEntity {
// 路由id
private String id;
// 路由断言集合
private List<PredicateEntity> predicates = new ArrayList<>();
// 路由过滤器集合
private List<FilterEntity> filters = new ArrayList<>();
// 路由转发的目标uri
private String uri;
// 路由执行的顺序
private int order = 0;
}
3: Add monitor
Monitor nacos configuration information
package com.carry.www.config;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.carry.www.entity.FilterEntity;
import com.carry.www.entity.PredicateEntity;
import com.carry.www.entity.RouteEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
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.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* 从Nacos获取动态路由
* 实现ApplicationEventPublisherAware发布接口来发布路由更新事件
*/
@Configuration
@Slf4j
public class DynamicRoutingConfig implements ApplicationEventPublisherAware {
@Value("${dynamic.route.server-addr}")
private String serverAddr;
@Value("${nacos.namespace}")
private String namespace;
@Value("${dynamic.route.data-id}")
private String dataId;
@Value("${dynamic.route.group}")
private String groupId;
// 保存、删除路由服务
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* @return
* @Author carryer
* @Description 获取nacos配置服务
* @Date
* @Param
**/
public ConfigService getNacosConfigInfo() throws Exception {
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR, serverAddr);
// properties.setProperty(PropertyKeyConst.USERNAME, username);
// properties.setProperty(PropertyKeyConst.PASSWORD, password);
properties.setProperty(PropertyKeyConst.NAMESPACE, namespace);
ConfigService configService = NacosFactory.createConfigService(properties);
return configService;
}
/**
* @return
* @Author carryer
* @Description 初始化路由
* @Date
* @Param
**/
@Bean
public void initRouting() {
try {
ConfigService configService = this.getNacosConfigInfo();
String configInfo = configService.getConfig(dataId, groupId, 5000);
if (null != configInfo) {
List<RouteEntity> list = JSONObject.parseArray(configInfo).toJavaList(RouteEntity.class);
for (RouteEntity route : list) {
update(assembleRouteDefinition(route));
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
e.printStackTrace();
}
}
/**
* @return
* @Author carryer
* @Description 刷新路由
* @Date
* @Param
**/
@Bean
public void refreshRouting() {
try {
ConfigService configService = this.getNacosConfigInfo();
//监听路由变化
configService.addListener(
dataId,
groupId,
new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
try {
log.info(configInfo);
if (null != configInfo) {
List<RouteEntity> list =
JSONObject.parseArray(configInfo).toJavaList(RouteEntity.class);
//更新路由表
for (RouteEntity route : list) {
update(assembleRouteDefinition(route));
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
e.printStackTrace();
}
}
});
} catch (Exception e) {
log.error(e.getMessage(), e);
e.printStackTrace();
}
}
/**
* @return
* @Author carryer
* @Description 路由更新
* @Date
* @Param
**/
private void update(RouteDefinition routeDefinition) throws Exception {
//先删除路由
routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
//再保存路由
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
//发布事件 发布者是RefreshRoutesEvent 事件是刷新路由
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
}
/**
* @return
* @Author carryer
* @Description 实体信息解析
* @Date
* @Param
**/
private RouteDefinition assembleRouteDefinition(RouteEntity routeEntity) {
RouteDefinition definition = new RouteDefinition();
// ID
definition.setId(routeEntity.getId());
// Predicates断言
List<PredicateDefinition> pdList = new ArrayList<>();
for (PredicateEntity predicateEntity : routeEntity.getPredicates()) {
PredicateDefinition predicateDefinition = new PredicateDefinition();
predicateDefinition.setArgs(predicateEntity.getArgs());
predicateDefinition.setName(predicateEntity.getName());
pdList.add(predicateDefinition);
}
definition.setPredicates(pdList);
// Filters过滤器
List<FilterDefinition> fdList = new ArrayList<>();
for (FilterEntity filterEntity : routeEntity.getFilters()) {
FilterDefinition filterDefinition = new FilterDefinition();
filterDefinition.setArgs(filterEntity.getArgs());
filterDefinition.setName(filterEntity.getName());
fdList.add(filterDefinition);
}
definition.setFilters(fdList);
// URI
URI uri = UriComponentsBuilder.fromUriString(routeEntity.getUri()).build().toUri();
definition.setUri(uri);
return definition;
}
}
The above code nacos can dynamically add routing, and friends can use it directly
============================= dividing line=================== ==========================
4: Relevant source code analysis
In the above configuration, first load the information in api-gatway.yaml, and then load the configuration information in routes-api-gatway.yaml. The principle is the same, but the listener is routes-api-gatway.yaml (there is json format)
ApplicationEventPublisherAware is an event publishing interface, including events, publishers, and listeners. The publisher of the event here is the class that comes with the gateway, RefreshRoutesEvent, and the event is to refresh the route.
Regardless of initializing the routing or refreshing the routing, the first step is to obtain the relevant information of nacos. ConfigService is obtained by the static factory class NacosFactory
Enter ConfigFactory and find that ConfigService is NacosConfigService obtained by reflection mechanism, NacosConfigService implements ConfigService
Enter NacosConfigService, the construction method inside carries out the relevant assignments of nacos, including namespaces and so on. So far, it can be seen that nacos has been successfully connected and the configuration class of nacos has been returned.
Let's look at the second sentence of code String configInfo = configService.getConfig(dataId, groupId, 5000) This sentence actually means that the nacos configuration class obtains configuration file information according to dataId and groupId, which is the yaml content you configure in the nacos visualization interface .
Look at the code below
Look directly at the getConfigInner method, this method first obtains the local Nacos configuration information, if not, then obtains the configuration information on the server
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
Obtain the configuration information on the nacos server.
String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);
Nacos的ClientWorker类进行赋值操作,调用getServerConfig方法获取配置返回配置数组
Enter ClientWorker's getServerConfig
The core method in ClientWorker agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout), click to enter ServerHttpAgent
ServerHttpAgent class for http call
The HttpSimpleClient class is called in ServerHttpAgent to call Nacos Open API to obtain configuration from Nacos Server. The
second time here is the url: http://xx.xx.xx.xx:8848/nacos/v1/cs/configs?dataId= routes-api-gatway&group=ROUTE_GROUP&tenant=dev , you can also call it with postman, in fact, what is returned is your configuration file of api-gatway.yaml or routes-api-gatway.yaml in nacos
The configuration file of routes-api-gatway.yaml in nacos
At this point, the first two lines of code have been executed. The following is to analyze and then publish the event.
String configInfo = configService.getConfig(dataId, groupId, 5000) The route string returned by the method is parsed into a collection of route entity classes, and the route is parsed and assigned to the RouteDefinition class in a loop,
Route, assertion, url and other settings in RouteDefinition
The following verification, add a new route with id of service4 and publish it, you will find that the code automatically reads the latest configuration and performs route analysis to update the route