Spring Cloud Gateway动态路由Apollo实现详解

 更新时间:2022年10月29日 17:17:02   作者:weihubeats  
这篇文章主要为大家介绍了Spring Cloud Gateway动态路由通过Apollo实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

背景

在之前我们了解的Spring Cloud Gateway配置路由方式有两种方式

  • 通过配置文件
spring:
  cloud:
    gateway:
      routes:
        - id: test
          predicates:
            - Path=/ms/test/*
          filters:
            - StripPrefix=2
          uri: http://localhost:9000
  • 通过JavaBean
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.path("/ms/test/**")
                .filters(f -> f.stripPrefix(2))
                .uri("http://localhost:9000"))
                .build();
    }

但是遗憾的是这两种方式都不支持动态路由,都需要重启服务。 所以我们需要对Spring Cloud Gateway进行改造,在改造的时候我们就需要看看源码了解下Spring Cloud Gateway的路由加载

路由的加载

我们之前分析了路由的加载主要在GatewayAutoConfigurationrouteDefinitionRouteLocator方法加载的

实际上最终获取的路由信息都是在GatewayProperties这个配置类中

所以我们在动态路由的时候修改GatewayProperties中的属性即可,即

List<RouteDefinition> routes

List<FilterDefinition> defaultFilters

恰巧Spring Cloud Gateway也提供了相应的getset方法

实际如果我们修改了该属性我们会发现并不会立即生效,因为我们会发现还有一个RouteLocator就是CachingRouteLocator,并且在配置Bean的时候加了注解@Primary,说明最后使用额RouteLocator实际是CachingRouteLocator

CachingRouteLocator最后还是使用RouteDefinitionRouteLocator类加载的,也是就我们上面分析的,看CachingRouteLocator就知道是缓存作用

这里引用网上一张加载图片

参考https://www.jb51.net/article/219238.htm

所以看到这里我们知道我们还需要解决的一个问题就是更新缓存,如何刷新缓存呢,这里Spring Cloud Gateway利用spring的事件机制给我提供了扩展

所以我们要做的事情就是这两件事:

  • GatewayProperties
  • 刷新缓存

实现动态路由

这里代码参考 github.com/apolloconfi…

@Component
@Slf4j
public class GatewayPropertiesRefresher implements ApplicationContextAware, ApplicationEventPublisherAware {
	private static final String ID_PATTERN = "spring\\.cloud\\.gateway\\.routes\\[\\d+\\]\\.id";
	private static final String DEFAULT_FILTER_PATTERN = "spring\\.cloud\\.gateway\\.default-filters\\[\\d+\\]\\.name";
	private ApplicationContext applicationContext;
	private ApplicationEventPublisher publisher;
	@Autowired
	private GatewayProperties gatewayProperties;
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
		this.publisher = applicationEventPublisher;
	}
	@ApolloConfigChangeListener(value = "route.yml",interestedKeyPrefixes = "spring.cloud.gateway.")
	public void onChange(ConfigChangeEvent changeEvent) {
		refreshGatewayProperties(changeEvent);
	}
	/***
	 * 刷新org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator中定义的routes
	 *
	 * @param changeEvent
	 * @return void
	 * @author ksewen
	 * @date 2019/5/21 2:13 PM
	 */
	private void refreshGatewayProperties(ConfigChangeEvent changeEvent) {
		log.info("Refreshing GatewayProperties!");
		preDestroyGatewayProperties(changeEvent);
		this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
		refreshGatewayRouteDefinition();
		log.info("GatewayProperties refreshed!");
	}
	/***
	 * GatewayProperties没有@PreDestroy和destroy方法
	 * org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#rebind(java.lang.String)中destroyBean时不会销毁当前对象
	 * 如果把spring.cloud.gateway.前缀的配置项全部删除(例如需要动态删除最后一个路由的场景),initializeBean时也无法创建新的bean,则return当前bean
	 * 若仍保留有spring.cloud.gateway.routes[n]或spring.cloud.gateway.default-filters[n]等配置,initializeBean时会注入新的属性替换已有的bean
	 * 这个方法提供了类似@PreDestroy的操作,根据配置文件的实际情况把org.springframework.cloud.gateway.config.GatewayProperties#routes
	 * 和org.springframework.cloud.gateway.config.GatewayProperties#defaultFilters两个集合清空
	 *
	 * @param
	 * @return void
	 * @author ksewen
	 * @date 2019/5/21 2:13 PM
	 */
	private synchronized void preDestroyGatewayProperties(ConfigChangeEvent changeEvent) {
		log.info("Pre Destroy GatewayProperties!");
		final boolean needClearRoutes = this.checkNeedClear(changeEvent, ID_PATTERN, this.gatewayProperties.getRoutes()
				.size());
		if (needClearRoutes) {
			this.gatewayProperties.setRoutes(new ArrayList<>());
		}
		final boolean needClearDefaultFilters = this.checkNeedClear(changeEvent, DEFAULT_FILTER_PATTERN, this.gatewayProperties.getDefaultFilters()
				.size());
		if (needClearDefaultFilters) {
			this.gatewayProperties.setDefaultFilters(new ArrayList<>());
		}
		log.info("Pre Destroy GatewayProperties finished!");
	}
	private void refreshGatewayRouteDefinition() {
		log.info("Refreshing Gateway RouteDefinition!");
		this.publisher.publishEvent(new RefreshRoutesEvent(this));
		log.info("Gateway RouteDefinition refreshed!");
	}
	/***
	 * 根据changeEvent和定义的pattern匹配key,如果所有对应PropertyChangeType为DELETED则需要清空GatewayProperties里相关集合
	 *
	 * @param changeEvent
	 * @param pattern
	 * @param existSize
	 * @return boolean
	 * @author ksewen
	 * @date 2019/5/23 2:18 PM
	 */
	private boolean checkNeedClear(ConfigChangeEvent changeEvent, String pattern, int existSize) {
		return changeEvent.changedKeys().stream().filter(key -> key.matches(pattern))
				.filter(key -> {
					ConfigChange change = changeEvent.getChange(key);
					return PropertyChangeType.DELETED.equals(change.getChangeType());
				}).count() == existSize;
	}
}

然后我们在apollo添加namespace:route.yml

配置内容如下:

spring:
  cloud:
    gateway:
      routes:
        - id: test
          predicates:
            - Path=/ms/test/*
          filters:
            - StripPrefix=2
          uri: http://localhost:9000

然后我们可以通过访问地址: http:localhost:8080/ms/test/health

看删除后是否是404,加上后是否可以正常动态路由

值得注意的是上面@ApolloConfigChangeListener中如果没有添加新的namespacevalue可以不用填写,如果配置文件是yml配置文件,在监听的时候需要指定文件后缀

以上就是Spring Cloud Gateway动态路由Apollo实现详解的详细内容,更多关于Spring Cloud Gateway Apollo的资料请关注脚本之家其它相关文章!

相关文章

  • Java将json对象转换为map键值对案例详解

    Java将json对象转换为map键值对案例详解

    这篇文章主要介绍了Java将json对象转换为map键值对案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09
  • 关于@MapperScan包扫描的坑及解决

    关于@MapperScan包扫描的坑及解决

    这篇文章主要介绍了关于@MapperScan包扫描的坑及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • java实现录音播放功能

    java实现录音播放功能

    这篇文章主要为大家详细介绍了java实现录音播放功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-08-08
  • java SpringBootWeb请求响应举例详解

    java SpringBootWeb请求响应举例详解

    SpringBoot是一种整合Spring技术栈的方式(或者说是框架),同时也是简化Spring的一种快速开发的脚手架,这篇文章主要给大家介绍了关于java SpringBootWeb请求响应的相关资料,需要的朋友可以参考下
    2024-05-05
  • java使用JMF实现音乐播放功能

    java使用JMF实现音乐播放功能

    这篇文章主要为大家详细介绍了java使用JMF实现音乐播放的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • Java8函数式接口的基础学习教程

    Java8函数式接口的基础学习教程

    这篇文章主要给大家介绍了关于Java8函数式接口基础学习的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • 深入浅析 Spring Security 缓存请求问题

    深入浅析 Spring Security 缓存请求问题

    这篇文章主要介绍了 Spring Security 缓存请求问题,本文通过实例文字相结合的形式给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友参考下吧
    2019-04-04
  • Spring示例讲解条件注入方法

    Spring示例讲解条件注入方法

    Spring支持按照条件来注入某些特定的bean,这也是Spring Boot实现自动化配置的底层方法,文中的示例代码讲解详细,需要的可以参考一下
    2022-06-06
  • 浅谈Java中实现深拷贝的两种方式—clone() & Serialized

    浅谈Java中实现深拷贝的两种方式—clone() & Serialized

    这篇文章主要介绍了Java中实现深拷贝的两种方式—clone() & Serialized,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • java实现一个简单的Web服务器实例解析

    java实现一个简单的Web服务器实例解析

    这篇文章主要介绍了java实现一个简单的Web服务器实例解析,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02

最新评论