在 ConfigurationProperties 更改后,使用 Spring @RefreshScope、@Conditional 批注在运行时替换 bean 注入 [英] Use Spring @RefreshScope, @Conditional annotations to replace bean injection at runtime after a ConfigurationProperties has changed
问题描述
在 ConfigurationProperties 更改后,我正在运行 PoC,以在运行时替换 bean 注入.这是基于 Spring Boot 动态配置属性支持以及由 Pivotal 的 Dave Syer 总结的此处.
I'm running a PoC around replacing bean injection at runtime after a ConfigurationProperties has changed. This is based on spring boot dynamic configuration properties support as well summarised here by Dave Syer from Pivotal.
在我的应用程序中,我有一个由两个不同的具体类实现的简单接口:
In my application I have a simple interface implemented by two different concrete classes:
@Component
@RefreshScope
@ConditionalOnExpression(value = "'${config.dynamic.context.country}' == 'it'")
public class HelloIT implements HelloService {
@Override
public String sayHello() {
return "Ciao dall'italia";
}
}
和
@Component
@RefreshScope
@ConditionalOnExpression(value = "'${config.dynamic.context.country}' == 'us'")
public class HelloUS implements HelloService {
@Override
public String sayHello() {
return "Hi from US";
}
}
spring cloud config server服务的application.yaml是:
application.yaml served by spring cloud config server is:
config:
name: Default App
dynamic:
context:
country: us
和相关的 ConfigurationProperties 类:
and the related ConfigurationProperties class:
@Configuration
@ConfigurationProperties (prefix = "config.dynamic")
public class ContextHolder {
private Map<String, String> context;
Map<String, String> getContext() {
return context;
}
public void setContext(Map<String, String> context) {
this.context = context;
}
我的客户端应用入口点是:
My client app entrypoint is:
@SpringBootApplication
@RestController
@RefreshScope
public class App1Application {
@Autowired
private HelloService helloService;
@RequestMapping("/hello")
public String hello() {
return helloService.sayHello();
}
我第一次浏览 http://locahost:8080/hello
端点时它返回Hi from US"
First time I browse http://locahost:8080/hello
endpoint it returns "Hi from US"
之后我在 spring 配置服务器的 application.yaml 中的 country: it
中更改 country: us
,然后点击 actuator/refresh
端点(在客户端应用上).
After that I change country: us
in country: it
in application.yaml in spring config server, and then hit the actuator/refresh
endpoint ( on the client app).
我第二次浏览 http://locahost:8080/hello
它仍然返回Hi from US"而不是我期望的ciao dall'italia".
Second time I browse http://locahost:8080/hello
it stills returns "Hi from US" instead of "ciao dall'italia" as I would expect.
使用@RefreshScope 时,spring boot 2 是否支持此用例?我特别指的是将它与 @Conditional 注释一起使用的事实.
Is this use case supported in spring boot 2 when using @RefreshScope? In particular I'm referring to the fact of using it along with @Conditional annotations.
推荐答案
这个实现对我有用:
@Component
@RefreshScope
public class HelloDelegate implements HelloService {
@Delegate // lombok delegate (for the sake of brevity)
private final HelloService delegate;
public HelloDelegate(
// just inject value from Spring configuration
@Value("${country}") String country
) {
HelloService impl = null;
switch (country) {
case "it":
this.delegate = new HelloIT();
break;
default:
this.delegate = new HelloUS();
break;
}
}
}
它的工作方式如下:
- 当第一次调用服务方法时,Spring 会创建 bean
HelloDelegate
并且配置在那一刻生效;bean 被放入刷新范围缓存 - 因为
@RefreshScope
每当配置改变时(country
属性,特别是在这种情况下)HelloDelegate
bean 从刷新范围缓存中清除立> - 当下次调用发生时,Spring 必须再次创建 bean,因为它不存在于缓存中,因此使用新的
country
属性重复步骤 1
- When first invocation of service method happens Spring creates bean
HelloDelegate
with configuration effective at that moment; bean is put into refresh scope cache - Because of
@RefreshScope
whenever configuration is changed (country
property particularly in this case)HelloDelegate
bean gets cleared from refresh scope cache - When next invocation happens, Spring has to create bean again because it does not exist in cache, so step 1 is repeated with new
country
property
据我观察这个实现的行为,如果 RefreshScope
bean 的配置没有被修改,Spring 会尽量避免重新创建它.
As far as I watched the behavior of this implementation, Spring will try to avoid recreating RefreshScope
bean if it's configuration was untouched.
当发现这个问题时,我正在寻找更通用的解决方案来进行这种运行时"实现替换.这种实现有一个明显的缺点:如果委托的 bean 具有复杂的非同构配置(例如,每个 bean 都有自己的属性),代码就会变得很糟糕,因此不安全.
I was looking for more generic solution of doing such "runtime" implementation replacement when found this question. This implementation has one significant disadvantage: if delegated beans have complex non-homogeneous configuration (e.g. each bean has it's own properties) code becomes lousy and therefore unsafe.
我使用这种方法为工件提供额外的可测试性.这样 QA 就能够在存根和真正的集成之间切换,而无需付出太多努力.我强烈建议避免将这种方法用于业务功能.
I use this approach to provide additional testability for artifacts. So that QA would be able to switch between stub and real integration without significant efforts. I would strongly recommend to avoid using such approach for business functionality.
这篇关于在 ConfigurationProperties 更改后,使用 Spring @RefreshScope、@Conditional 批注在运行时替换 bean 注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!