spring cloud:config-server中@RefreshScope的"陷阱"
spring cloud的config-serfver主要用於提供分散式的配置管理,其中有一個重要的註解:@RefreshScope,如果程式碼中需要動態重新整理配置,在需要的類上加上該註解就行。但某些複雜的注入場景下,這個註解使用不當,配置可能仍然不動態重新整理,比如下面的場景:
1. 先定義一個配置類(假設這裡面定義了一個apiUrl,表示呼叫的api地址)
@Component @ConfigurationProperties(prefix = "demo.app") @Data @RefreshScope public class DemoServiceAppConfig { /** * api呼叫地址 */ private String apiUrl = ""; }
對應的yml配置類似:
demo: app: apiUrl: "http://11111.com/xxxxx"
2. 然後定義一個工具類,用於封裝呼叫外部api
@Data @RefreshScope public class TestUtil { private String apiUrl; public void callApi() { System.out.println("apiUrl:" + apiUrl); } }
3. 為了避免1中的配置類,與2中的工具類強耦合,搞一個bean注入容器把他們關聯起來
@Component @RefreshScope public class BeanContainer { @Autowired DemoServiceAppConfig appConfig; @Bean private TestUtil testUtil() { TestUtil testUtil = new TestUtil(); testUtil.setApiUrl(appConfig.getApiUrl()); return testUtil; } }
4 最後來一個Controller測試下
@RestController @RefreshScope @Api(consumes = "application/json", produces = "application/json", protocols = "http", basePath = "/") public class PingController extends AbstractController { @Autowired DemoServiceAppConfig appConfig; @Autowired TestUtil testUtil; @RequestMapping(value = "/test", method = {RequestMethod.GET, RequestMethod.POST}) public String test() { return "config.apiUrl=>" + appConfig.getApiUrl() + "<br/>testUtil.apiUrl=>" + testUtil.getApiUrl(); } }
注:上面所有這些類,都加了@RefreshScope標籤。
跑起來,效果如下:
然後把yml檔案改下,然後push到git上,再curl -X POST http://localhost:7031/refresh 刷一把配置
可以看到,通過testUtil呼叫的方法中,取到的apiUrl值仍然是舊的,並沒有動態重新整理!
正確姿勢如下:
最後一個問題,@RefreshScope作用的類,不能是final類,否則啟動時會報錯,類似下面這堆:
Caused by: java.lang.IllegalArgumentException: Cannot subclass final class TestUtil
at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:565) ~[spring-core-4.3.9.RELEASE.jar:4.3.9.RELEASE]
從出錯資訊上看,底層應該是使用cglib進行增強,需要在TestUtil下派生子類。
然後,由cglib又引出了更一個坑,如果在一些web核心元件相關的config上誤加了@RefreshScope, 比如下面這樣:
@Bean @RefreshScope public CorsFilter corsFilter() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); final CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); config.addAllowedMethod("OPTIONS"); config.addAllowedMethod("HEAD"); config.addAllowedMethod("GET"); config.addAllowedMethod("PUT"); config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE"); config.addAllowedMethod("PATCH"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); }
這裡面有一個org.springframework.web.cors.CorsConfiguration配置類,加了@RefreshScope後,org.springframework.web.filter.GenericFilterBean#init 這個核心bean的init就會報錯,要麼應用啟不起來,要麼請求時報內部錯誤。
最後,還有一個要注意的坑,比如:
abc: "xxx"
如果yml檔案中有一個這樣的屬性,改成:
abc: ""
即變成空後,就算再curl -X POST http://.../refresh 介面,程式碼中拿到的值,仍然是xxx,建議如果要讓一個屬性值失效,可以約定一個特定值,比如
abc:"NULL"
然後程式碼中用“NULL”來判斷.