写这篇博客主要是为了汇总下动态路由的多种实现方式,没有好坏之分,任何的方案都是依赖业务场景需求的,现在网上实现方式主要有: 基于Nacos, 基于数据库(PosgreSQL/Redis), 基于Memory(内存),而我们公司是第四种方案:基于File(本地文件),通过不同文件来隔离不同业务线的路由,大佬们不要喷,任何方案脱离不了业务场景(各种难言之隐)。下面主要简单介绍下这四种动态路由的实现方式
1.基于Nacos的动态路由Nacos官方简介
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。主要特性如下:
1. 服务发现和服务健康监测 2. 动态配置服务 3. 动态 DNS 服务 4. 服务及其元数据管理
此处不展开介绍Nacos了,主要讲下Spring Cloud Gateway + Nacos 实现动态路由
1.1 相关版本如下spring-cloud-starter-gateway:2.1.0.RELEASE spring-cloud-starter-alibaba-nacos-config:2.2.5.RELEASE1.2 实现思路
ok,上代码
properties配置
### nacos configuration start spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.discovery.namespace=50f5dcf0-f3c0-4c79-9715-0e25e3959ssd nacos.gateway.route.config.data-id=server-routes nacos.gateway.route.config.group=spb-gateway ### nacos configuration end
NacosGatewayConfig配置类
package com.kawa.spbgateway.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class NacosGatewayConfig { public static final long DEFAULT_TIMEOUT = 30000; public static String NACOS_SERVER_ADDR; public static String NACOS_NAMESPACE; public static String NACOS_ROUTE_DATA_ID; public static String NACOS_ROUTE_GROUP; @Value("${spring.cloud.nacos.discovery.server-addr}") public void setNacosServerAddr(String nacosServerAddr) { NACOS_SERVER_ADDR = nacosServerAddr; } @Value("${spring.cloud.nacos.discovery.namespace}") public void setNacosNamespace(String nacosNamespace) { NACOS_NAMESPACE = nacosNamespace; } @Value("${nacos.gateway.route.config.data-id}") public void setNacosRouteDataId(String nacosRouteDataId) { NACOS_ROUTE_DATA_ID = nacosRouteDataId; } @Value("${nacos.gateway.route.config.group}") public void setNacosRouteGroup(String nacosRouteGroup) { NACOS_ROUTE_GROUP = nacosRouteGroup; } @Bean public ObjectMapper getObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); return objectMapper; } }
NacosDynamicRouteService类
加载和监听路由
package com.kawa.spbgateway.service; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.config.listener.Listener; import com.alibaba.nacos.api.exception.NacosException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.kawa.spbgateway.route.CustomizedRouteDefinition; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.List; import java.util.Properties; import java.util.concurrent.Executor; import static com.kawa.spbgateway.config.NacosGatewayConfig.*; @Service @Slf4j @DependsOn({"nacosGatewayConfig"}) public class NacosDynamicRouteService { @Autowired private NacosRefreshRouteService nacosRefreshRouteService; private ConfigService configService; @Autowired private ObjectMapper objectMapper; @PostConstruct public void init() { log.info(">>>>>>>>>> init gateway route <<<<<<<<<<"); configService = initConfigService(); if (null == configService) { log.error(">>>>>>> init the ConfigService failed!!!"); } String configInfo = null; try { configInfo = configService.getConfig(NACOS_ROUTE_DATA_ID, NACOS_ROUTE_GROUP, DEFAULT_TIMEOUT); log.info(">>>>>>>>> get the gateway configInfo: {}", configInfo); ListrouteDefinitions = objectMapper.readValue(configInfo, new TypeReference() { }); for (RouteDefinition definition : routeDefinitions) { log.info(">>>>>>>>>> load route : {}", definition.toString()); nacosRefreshRouteService.add(definition); } } catch (NacosException | JsonProcessingException e) { e.printStackTrace(); } dynamicRouteByNacosListener(NACOS_ROUTE_DATA_ID, NACOS_ROUTE_GROUP); } private void dynamicRouteByNacosListener(String dataId, String group) { try { configService.addListener(dataId, group, new Listener() { @Override public Executor getExecutor() { log.info("-------------------getExecutor-------------------"); return null; } @Override public void receiveConfigInfo(String configInfo) { log.info(">>>>>>>>> listened configInfo change: {}", configInfo); ListrouteDefinitions = null; try { routeDefinitions = objectMapper.readValue(configInfo, new TypeReference<>() { }); } catch (JsonProcessingException e) { e.printStackTrace(); } nacosRefreshRouteService.updateList(routeDefinitions); } }); } catch (NacosException e) { e.printStackTrace(); } } private ConfigService initConfigService() { Properties properties = new Properties(); properties.setProperty("serverAddr", NACOS_SERVER_ADDR); properties.setProperty("namespace", NACOS_NAMESPACE); try { return NacosFactory.createConfigService(properties); } catch (NacosException e) { e.printStackTrace(); return null; } } }
NacosRefreshRouteService类
实现路由的更新和刷新本地缓存
package com.kawa.spbgateway.service; import com.kawa.spbgateway.route.CustomizedRouteDefinition; 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.RouteDefinitionLocator; import org.springframework.cloud.gateway.route.RouteDefinitionWriter; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import reactor.core.publisher.Mono; import java.util.ArrayList; import java.util.List; @Service @Slf4j public class NacosRefreshRouteService implements ApplicationEventPublisherAware { private ApplicationEventPublisher publisher; @Autowired private RouteDefinitionWriter routeDefinitionWriter; @Autowired private RouteDefinitionLocator routeDefinitionLocator; private ListrouteIds = new ArrayList<>(); @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.publisher = applicationEventPublisher; } /** * 删除路由 * * @param id * @return */ public void delete(String id) { try { log.info(">>>>>>>>>> gateway delete route id {}", id); this.routeDefinitionWriter.delete(Mono.just(id)).subscribe(); this.publisher.publishEvent(new RefreshRoutesEvent(this)); } catch (Exception e) { e.printStackTrace(); } } /** * 更新路由 * * @param definitions * @return */ public void updateList(Listdefinitions) { log.info(">>>>>>>>>> gateway update route {}", definitions); // 删除缓存routerDefinition ListrouteDefinitionsExits = routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst(); if (!CollectionUtils.isEmpty(routeDefinitionsExits)) { routeDefinitionsExits.forEach(routeDefinition -> { log.info("delete routeDefinition:{}", routeDefinition); delete(routeDefinition.getId()); }); } definitions.forEach(definition -> { updateById(definition); }); } /** * 更新路由 * * @param definition * @return */ public void updateById(RouteDefinition definition) { try { log.info(">>>>>>>>>> gateway update route {}", definition); this.routeDefinitionWriter.delete(Mono.just(definition.getId())); } catch (Exception e) { e.printStackTrace(); } try { routeDefinitionWriter.save(Mono.just(definition)).subscribe(); this.publisher.publishEvent(new RefreshRoutesEvent(this)); } catch (Exception e) { e.printStackTrace(); } } /** * 增加路由 * * @param definition * @return */ public void add(RouteDefinition definition) { log.info(">>>>>>>>>> gateway add route {}", definition); routeDefinitionWriter.save(Mono.just(definition)).subscribe(); this.publisher.publishEvent(new RefreshRoutesEvent(this)); } }
测试一下
nacos添加路由配置,注意"Data ID" 和 “Group”要和配置一一对应
启动项目加载配置,可以看到加载路由配置的日志(监听路由变化的日志就不截图了)
也可以通过actuator的接口测试下(可以看到已经路由已经加载到本地内存)
基于数据库,关系型数据库和非关系型数据库,实现思路是一样的,这里我就以Redis来举例子
2.1 相关配置org.springframework.bootspring-boot-starter-data-redis2.2 实现思路
上代码
proerties配置
### redis configuration start spring.redis.database=0 spring.redis.host=127.0.0.1 spring.redis.port=10619 spring.redis.password=asdqwe ### redis configuratiokn end
RedisConfiguration类
package com.kawa.spbgateway.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfiguration { @Bean public StringRedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate redisTemplate = new StringRedisTemplate(); //设置工厂链接 redisTemplate.setConnectionFactory(redisConnectionFactory); //设置自定义序列化方式 setSerializeConfig(redisTemplate); return redisTemplate; } private void setSerializeConfig(StringRedisTemplate redisTemplate) { StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(stringRedisSerializer); redisTemplate.setHashKeySerializer(stringRedisSerializer); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); jackson2JsonRedisSerializer.setObjectMapper(om); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); } }
RedisDynamicRouteService类
操作redis的类
package com.kawa.spbgateway.service; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.support.NotFoundException; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.ArrayList; import java.util.List; @Slf4j @Service public class RedisDynamicRouteService { public static final String GATEWAY_ROUTES_PREFIX = "brian:sz_home:gateway_dynamic_route:"; @Autowired private StringRedisTemplate redisTemplate; @Autowired private ObjectMapper objectMapper; public FluxgetRouteDefinitions() { log.info(">>>>>>>>>> getRouteDefinitions <<<<<<<<<<"); ListrouteDefinitions = new ArrayList<>(); redisTemplate.keys(GATEWAY_ROUTES_PREFIX+"*").stream().forEach(key -> { String rdStr = redisTemplate.opsForValue().get(key); RouteDefinition routeDefinition = null; try { routeDefinition = objectMapper.readValue(rdStr, RouteDefinition.class); routeDefinitions.add(routeDefinition); } catch (JsonProcessingException e) { e.printStackTrace(); } }); return Flux.fromIterable(routeDefinitions); } public Monosave(Monoroute) { return route.flatMap(routeDefinition -> { String rdStr = null; try { rdStr = objectMapper.writeValueAsString(routeDefinition); redisTemplate.opsForValue().set(GATEWAY_ROUTES_PREFIX + routeDefinition.getId(), rdStr); } catch (JsonProcessingException e) { e.printStackTrace(); } return Mono.empty(); }); } public Monodelete(MonorouteId) { return routeId.flatMap(id -> { if (redisTemplate.hasKey(GATEWAY_ROUTES_PREFIX + id)) { redisTemplate.delete(GATEWAY_ROUTES_PREFIX + id); return Mono.empty(); } return Mono.defer(() -> Mono.error(new NotFoundException("routeDefinition not found, id is: " + id))); }); } public Monoget(MonorouteId) { return routeId.flatMap(id -> Mono.just(redisTemplate.hasKey(GATEWAY_ROUTES_PREFIX + id))); } }
RedisRefreshRouteService类
动态刷新路由
package com.kawa.spbgateway.service; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; 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 org.springframework.util.Assert; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @Slf4j @Service public class RedisRefreshRouteService implements ApplicationEventPublisherAware, ApplicationRunner { @Autowired private RedisDynamicRouteService repository; @Autowired private RouteDefinitionWriter routeDefinitionWriter; private ApplicationEventPublisher publisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.publisher = applicationEventPublisher; } private void loadRoutes(){ log.info(">>>>>>>>>> init routes from redis <<<<<<<<<<"); FluxrouteDefinitions = repository.getRouteDefinitions(); routeDefinitions.subscribe(r-> { routeDefinitionWriter.save(Mono.just(r)).subscribe(); }); publisher.publishEvent(new RefreshRoutesEvent(this)); } public void add(RouteDefinition routeDefinition){ Assert.notNull(routeDefinition.getId(),"routeDefinition is can not be null"); repository.save(Mono.just(routeDefinition)).subscribe(); routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe(); publisher.publishEvent(new RefreshRoutesEvent(this)); } public void update(RouteDefinition routeDefinition){ Assert.notNull(routeDefinition.getId(),"routeDefinition is can not be null"); repository.delete(Mono.just(routeDefinition.getId())).subscribe(); routeDefinitionWriter.delete(Mono.just(routeDefinition.getId())).subscribe(); repository.save(Mono.just(routeDefinition)).subscribe(); routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe(); publisher.publishEvent(new RefreshRoutesEvent(this)); } public void delete(String id){ Assert.notNull(id,"routeDefinition is can not be null"); repository.delete(Mono.just(id)).subscribe(); routeDefinitionWriter.delete(Mono.just(id)).subscribe(); publisher.publishEvent(new RefreshRoutesEvent(this)); } @Override public void run(ApplicationArguments args) throws Exception { loadRoutes(); } }
RedisDynamicRouteController类
package com.kawa.spbgateway.controller; import com.kawa.spbgateway.service.RedisRefreshRouteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Mono; @RestController @RequestMapping("/local") public class RedisDynamicRouteController { @Autowired private RedisRefreshRouteService dynamicRouteService; @PostMapping("/add") public Monocreate(@RequestBody RouteDefinition entity) { dynamicRouteService.add(entity); return Mono.just(new ResponseEntity<>("save success", HttpStatus.OK)); } @PostMapping("/update") public Monoupdate(@RequestBody RouteDefinition entity) { dynamicRouteService.update(entity); return Mono.just(new ResponseEntity<>("update success", HttpStatus.OK)); } @PostMapping("/delete/{id}") public Monodelete(@PathVariable String id) { dynamicRouteService.delete(id); return Mono.just(new ResponseEntity<>("delete success", HttpStatus.OK)); } }
ok,测试下
启动项目,查询下actuator接口,http://localhost:8080/actuator/gateway/routedefinitions 没有任何RouteDefinition
postman插入一条RouteDefinition信息,http://127.0.0.1:8080/local/add
再次查询RouteDefinitions信息,可以看到新添加进来的路由
ok,测试下路由是否生效
可以看到接口有数据返回,日志信息发现通过接口添加的路由生效了,转发到了目标接口
接下来删除路由继续测试下
调用删除接口后,通过actuator查询确认路由被删除了
再次测试目标接口,404 Not Found
基于本地内存的方式比较简单,Spring Boot已经提供了两个组件Spring Boot Admin 和 Spring Boot Actuator,我这边只用Actuator来实现路由动态变化
3.1 相关配置和接口org.springframework.bootspring-boot-starter-actuator
o.s.c.g.a.GatewayControllerEndpoint: {GET /routes/{id}}: route(String) {GET /routes}: routes() {GET /routedefinitions}: routesdef() {GET /globalfilters}: globalfilters() {GET /routefilters}: routefilers() {GET /routepredicates}: routepredicates() {GET /routes/{id}/combinedfilters}: combinedfilters(String) {DELETE /routes/{id}}: delete(String) {POST /routes/{id}}: save(String,RouteDefinition) {POST /refresh}: refresh()3.2 实现思路
和上面一样核心接口,routeDefinitionWriter.save(), routeDefinitionWriter.delete(),publisher.publishEvent(new RefreshRoutesEvent(this))
测试一下
项目启动的时候,不配置任何路由, 测试接口http://127.0.0.1:8080/actuator/gateway/routedefinitions 没有任何信息
尝试添加一条路由信息,http://127.0.0.1:8080/actuator/gateway/routes/org.springframework.util.AlternativeJdkIdGenerator@3f203441
最后测试下,路由有没有添加到内存,先刷新缓存http://127.0.0.1:8080/actuator/gateway/refresh,再次请求http://127.0.0.1:8080/actuator/gateway/routedefinitions
可以发现路由已经到本地内存了,目标路由这里就不测试了,下面的基于File的动态路由会再次测试目标路由
4.基于本地File的动态路由 4.1 实现思路
上代码
route配置yml
根据不用业务通过文件名区分开
card-hk.yml
routes: - uri: http://card-hk.${gateway.route.domain.postfix} predicates: - Path=/api/hk/card/v1/uuu/query - Method=POST - uri: http://card-hk.${gateway.route.domain.postfix} predicates: - Path=/api/hk/card/v1/er/query - Method=POST
pancake.yml
routes: - uri: http://pancake.${gateway.route.domain.postfix} predicates: - Path=^/api/pancake/v1/*,^/api/pancake/v1/*/query - predicates: - Path=/api/pancake/v1/coin/query filters: - RewritePath=/api/pancake/v1/coin/query, /api/v1/coin/query
passport-hk.yml
routes: - uri: http://passport-hk.${gateway.route.domain.postfix} predicates: - Path=/api/passport-hk/v1/passport/query auths: - sms
FileRefreshRouteService类
实现定时任务,启动刷新路由
package com.kawa.spbgateway.service; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.cloud.gateway.event.RefreshRoutesEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.io.IOException; @Slf4j @Service public class FileRefreshRouteService implements ApplicationEventPublisherAware, CommandLineRunner { @Autowired private FileDynamicRouteService routeService; @Autowired private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } @Scheduled(cron = "0/5 * * * * ?") private void autoRefresh() { refreshRoute(); } private synchronized void refreshRoute() { try { log.info(">>>>>>>>>> start refresh route <<<<<<<<<<"); if (routeService.refreshRoutes()) { log.info(")))))))))))))))))))))))))))))) FileRefreshRouteService refreshRoute~~~"); applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this)); } } catch (IOException e) { log.error("Refresh route failed :{}", e.getMessage()); throw new IllegalStateException("Refresh route failed :{}", e); } } @Override public void run(String... args) { refreshRoute(); } }
FileDynamicRouteService类
实现路由刷新的功能,包括checksum路由文件是否修改,是否更新路由
package com.kawa.spbgateway.service; import com.kawa.spbgateway.domain.BrianGatewayProperties; import com.kawa.spbgateway.property.RefreshRoutePropertySource; import com.kawa.spbgateway.transformer.BrianRouteDefinitionTransformer; import com.kawa.spbgateway.util.ChecksumUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.env.PropertySourceLoader; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.route.RouteDefinitionRepository; import org.springframework.cloud.gateway.support.NotFoundException; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import static com.kawa.spbgateway.content.Contents.*; @Service @Slf4j public class FileDynamicRouteService implements RouteDefinitionRepository { private Environment environment; private String folder; private ListresourceFileExt; private ConcurrentHashMap fileChecksumMap = new ConcurrentHashMap<>(32); private ConcurrentHashMap routes = new ConcurrentHashMap<>(32); private BrianRouteDefinitionTransformer transformer = new BrianRouteDefinitionTransformer(); public FileDynamicRouteService(Environment environment) { this.environment = environment; } public boolean refreshRoutes() throws IOException { getAndInitProperties(); Listresources = getCustomizedConfigs(); if (isRefresh(resources)) { updateFileChecksumMap(resources); updateRefreshRoutePropertySource(resources); refreshRouteCache(readRouteConfig(resources)); return true; } log.info(">>>>>>>>>> no need refresh route <<<<<<<<<<"); return false; } /** * @param targets */ private void refreshRouteCache(Listtargets) { // when first load the RouteDefinition if (CollectionUtils.isEmpty(routes)) { targets.forEach(rd -> { // add routeDefinition save(Mono.just(rd)).subscribe(); log.info(">>>>>>>>>> init add routeDefinition:{}", rd); }); return; } Listdefinitions = new ArrayList<>(); Collections.addAll(definitions, new RouteDefinition[routes.size()]); Collections.copy(definitions, routes.values().stream().collect(Collectors.toList())); targets.forEach(rd -> { if (Objects.isNull(routes.get(rd.getId()))) { // add new RouteDefinition save(Mono.just(rd)).subscribe(); log.info(">>>>>>>>>> add routeDefinition:{}", rd); } // not null don't update if (Objects.nonNull(routes.get(rd.getId())) && rd.equals(routes.get(rd.getId()))) { definitions.remove(rd); } }); // remove RouteDefinition if (Objects.nonNull(definitions)) { definitions.forEach(rd -> { delete(Mono.just(rd.getId())).subscribe(); log.info(">>>>>>>>>> delete routeDefinition:{}", rd); }); } } private ListreadRouteConfig(Listresources) { Binder binder = Binder.get(environment); Listconfigs = new ArrayList<>(); resources.stream().map(res -> res.getFilename()).forEach(fn -> { if (!fn.isEmpty()) { log.info(">>>>>>>>>> BrianGatewayProperties filename:{}", fn); BrianGatewayProperties brianGatewayProperties = binder.bindOrCreate(fn, BrianGatewayProperties.class); log.info(">>>>>>>>>> {}", brianGatewayProperties); brianGatewayProperties.getRoutes().forEach(route -> { configs.add(transformer.transform(route, route.getUri() == null ? null : route.getUri().toString())); }); } }); return configs; } private void updateRefreshRoutePropertySource(Listresources) { if (environment instanceof ConfigurableEnvironment) { MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources(); ListpropertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); if (null != folder) { resources.forEach(res -> { addCustomizedResource(propertySources, res, propertySourceLoaders); }); } } } /** * @param propertySources * @param resource * @param propertySourceLoaders * @return */ private void addCustomizedResource(MutablePropertySources propertySources, Resource resource, ListpropertySourceLoaders) { propertySourceLoaders.forEach(psl -> { ListfileExts = Arrays.asList(psl.getFileExtensions()); String filename = resource.getFilename(); if (fileExts.contains(StringUtils.getFilenameExtension(filename))) { log.info(">>>>>>>>>> load file resource: {}", filename); try { List关注打赏


微信扫码登录