天天看點

啟用 Spring-Cloud-OpenFeign 配置可重新整理,項目無法啟動,我 TM 人傻了(下)Spring Cloud 中的配置動态重新整理問題定位解決問題

啟用 Spring-Cloud-OpenFeign 配置可重新整理,項目無法啟動,我 TM 人傻了(下)Spring Cloud 中的配置動态重新整理問題定位解決問題

本篇文章涉及底層設計以及原理,以及問題定位,比較深入,篇幅較長,是以拆分成上下兩篇:

上:問題簡單描述以及 Spring Cloud RefreshScope 的原理

下:目前 spring-cloud-openfeign + spring-cloud-sleuth 帶來的 bug 以及如何修複

其實在測試的程式中,我們已經實作了一個簡單的 Bean 重新整理的設計。Spring Cloud 的自動重新整理中,包含兩種元素的重新整理,分别是:

配置重新整理,即 <code>Environment.getProperties</code> 和 <code>@ConfigurationProperties</code> 相關 Bean 的重新整理

添加了 <code>@RefreshScope</code> 注解的 Bean 的重新整理

<code>@RefreshScope</code> 注解其實和我們上面自定義 Scope 使用的注解配置類似,即指定名稱為 <code>refresh</code>,同時使用 CGLIB 代理:

<code>RefreshScope</code>

同時需要自定義 Scope 進行注冊,這個自定義的 Scope 即 <code>org.springframework.cloud.context.scope.refresh.RefreshScope</code>,他繼承了 <code>GenericScope</code>,我們先來看這個父類,我們專注我們前面測試的那三個 Scope 接口方法,首先是 get:

然後是注冊 Destroy 的回調,其實就放在對應的 Bean 中,在移除的時候,會調用這個回調:

最後是移除 Bean,就更簡單了,從緩存中移除這個 Bean:

這樣,如果緩存中的 bean 被移除,下次調用 get 的時候,就會重新生成 Bean。并且,由于 RefreshScope 注解中預設的 ScopedProxyMode 為 CGLIB 代理模式,是以每次通過 BeanFactory 擷取 Bean 以及自動裝載的 Bean 調用的時候,都會調用這裡 Scope 的 get 方法。

Spring Cloud 将動态重新整理接口通過 Spring Boot Actuator 進行暴露,對應路徑是 <code>/actuator/refresh</code>,對應源碼是:

<code>RefreshEndpoint</code>

可以看出其核心是 ContextRefresher,他的核心邏輯也非常簡單:

<code>ContextRefresher</code>

調用 RefreshScope 的 RefreshAll,其實就是調用我們上面說的 GenericScope 的 destroy,之後釋出 RefreshScopeRefreshedEvent:

GenericScope 的 destroy 其實就是将緩存清空,這樣所有标注 <code>@RefreshScope</code> 注解的 Bean 都會被重建。

通過上篇的源碼分析,我們知道,如果想實作 Feign.Options 的動态重新整理,目前我們不能把它放入 NamedContextFactory 生成的 ApplicationContext 中,而是需要将它放入項目的根 ApplicationContext 中,這樣 Spring Cloud 暴露的 refresh actuator 接口,才能正确重新整理。spring-cloud-openfeign 中,也是這麼實作的。

如果配置了

那麼在初始化每個 FeignClient 的時候,就會将 Feign.Options 這個 Bean 注冊到根 ApplicationContext,對應源碼:

<code>FeignClientsRegistrar</code>

這樣,在調用 <code>/actuator/refresh</code> 接口的時候,這些 Feign.Options 也會被重新整理。但是注冊到根 ApplicationContext 中的話,對應的 FeignClient 如何擷取這個 Bean 使用呢?即在 Feign 的 NamedContextFactory (即 FeignContext )中生成的 ApplicationContext 中,如何找到這個 Bean 呢?

這個我們不用擔心,因為所有的 NamedContextFactory 生成的 ApplicationContext 的 parent,都設定為了根 ApplicationContext,參考源碼:

這樣設定後,FeignClient 在自己的 ApplicationContext 中如果找不到的話,就會去 parent 的 ApplicationContext 也就是根 ApplicationContext 去找。

這樣看來,設計是沒問題的,但是我們的項目啟動不了,應該是啟用其他依賴導緻的。

我們在擷取 Feign.Options Bean 的地方打斷點調試,發現并不是直接從 FeignContext 中擷取 Bean,而是從 spring-cloud-sleuth 的 TraceFeignContext 中擷取的。

spring-cloud-sleuth 為了保持鍊路,在很多地方增加了埋點,對于 OpenFeign 也不例外。在 <code>FeignContextBeanPostProcessor</code>,将 FeignContext 包裝了一層變成了 TraceFeignContext:

這樣,FeignClient 會從這個 TraceFeignContext 中讀取 Bean,而不是 FeignContext。但是通過源碼我們發現,TraceFeignContext 并沒有設定 parent 為根 ApplicationContext,是以找不到注冊到根 ApplicationContext 中的 Feign.Options 這些 Bean。

針對這個 Bug,我向 spring-cloud-sleuth 和 spring-cloud-commons 分别提了修改:

add getter for parent in NamedContextFactory

fix #2023, add parent in the new TraceFeignContext

大家如果在項目中使用了 spring-cloud-sleuth,對于 spring-cloud-openfeign 想開啟自動重新整理的話,可以考慮使用同名同路徑的類替換代碼先解決這個問題。等待我送出的代碼釋出新版本了。

參考代碼:

微信搜尋“我的程式設計喵”關注公衆号,每日一刷,輕松提升技術,斬獲各種offer:
啟用 Spring-Cloud-OpenFeign 配置可重新整理,項目無法啟動,我 TM 人傻了(下)Spring Cloud 中的配置動态重新整理問題定位解決問題

繼續閱讀