背景
在之前我们了解的Spring Cloud Gateway
配置路由方式有两种方式
- 通过配置文件
1 2 3 4 5 6 7 8 9 10 | spring: cloud: gateway: routes: - id : test predicates: - Path=/ms/test/* filters: - StripPrefix=2 uri: http : //localhost : 9000 |
- 通过JavaBean
1 2 3 4 5 6 7 8 | @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes() .route(r -> r.path( "/ms/test/**" ) .filters(f -> f.stripPrefix( 2 )) .build(); } |
但是遗憾的是这两种方式都不支持动态路由,都需要重启服务。 所以我们需要对Spring Cloud Gateway
进行改造,在改造的时候我们就需要看看源码了解下Spring Cloud Gateway
的路由加载
路由的加载
我们之前分析了路由的加载主要在GatewayAutoConfiguration
的routeDefinitionRouteLocator
方法加载的
实际上最终获取的路由信息都是在GatewayProperties
这个配置类中
所以我们在动态路由的时候修改GatewayProperties
中的属性即可,即
List routes
List defaultFilters
恰巧Spring Cloud Gateway
也提供了相应的get
、set
方法
实际如果我们修改了该属性我们会发现并不会立即生效,因为我们会发现还有一个RouteLocator
就是CachingRouteLocator
,并且在配置Bean的时候加了注解@Primary
,说明最后使用额RouteLocator
实际是CachingRouteLocator
CachingRouteLocator
最后还是使用RouteDefinitionRouteLocator
类加载的,也是就我们上面分析的,看CachingRouteLocator
就知道是缓存作用
这里引用网上一张加载图片
参考https://www.2it.club/article/219238.htm
所以看到这里我们知道我们还需要解决的一个问题就是更新缓存,如何刷新缓存呢,这里Spring Cloud Gateway
利用spring的事件机制给我提供了扩展
所以我们要做的事情就是这两件事:
GatewayProperties
- 刷新缓存
实现动态路由
这里代码参考 github.com/apolloconfi…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | @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
配置内容如下:
1 2 3 4 5 6 7 8 9 10 | 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
中如果没有添加新的namespace
,value
可以不用填写,如果配置文件是yml配置文件,在监听的时候需要指定文件后缀
以上就是Spring Cloud Gateway动态路由Apollo实现详解的详细内容,更多关于Spring Cloud Gateway Apollo的资料请关注IT俱乐部其它相关文章!