config-server中@RefreshScope的"陷阱"
Posted 菩提树下的杨过
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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”来判断.
以上是关于config-server中@RefreshScope的"陷阱"的主要内容,如果未能解决你的问题,请参考以下文章
第五篇 高可用配置中心config-server(SVN版)
spring cloud config-server .properties 内容协商失败
使用 Spring-Cloud-Connectors 配置 Spring Boot 以使用 PWS Config-Server
springCloud学习-高可用的分布式配置中心(Spring Cloud Config)