【问题标题】:Use Spring @RefreshScope, @Conditional annotations to replace bean injection at runtime after a ConfigurationProperties has changed在 ConfigurationProperties 发生更改后,使用 Spring @RefreshScope、@Conditional 注释在运行时替换 bean 注入
【发布时间】:2019-01-31 03:48:59
【问题描述】:

在 ConfigurationProperties 更改后,我正在运行一个 PoC,以在运行时替换 bean 注入。这是基于 Spring Boot 动态配置属性支持以及来自 Pivotal 的 Dave Syer 总结的 here

在我的应用程序中,我有一个由两个不同的具体类实现的简单接口:

@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是:

config:
  name: Default App
  dynamic:
    context:
      country: us

以及相关的 ConfigurationProperties 类:

@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;
}

我的客户端应用入口点是:

@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”

之后,我在 spring 配置服务器的 application.yaml 中将 country: it 中的 country: us 更改为 actuator/refresh 端点(在客户端应用程序上)。

我第二次浏览 http://locahost:8080/hello 时,它仍然返回“Hi from US”,而不是我预期的“ciao dall'italia”。

使用@RefreshScope 时,Spring Boot 2 是否支持此用例?特别是我指的是与@Conditional 注释一起使用它的事实。

【问题讨论】:

  • @ConditionalOn* 注解用于@Configuration 类,而不是直接用于bean。配置不会在刷新范围内重新运行,只会重新创建单个 bean。我会将@ConfigurationProperties 注入单个bean 以获得所需的效果。 @RefreshScope@Configuration 类上也表现不佳,它仅适用于单个 bean。
  • @spencergibb 我不确定我是否理解注入@ConfigurationProperties 会获得相同的效果。查看我的示例,您是说将其注入 App1Application bean(具有 @RefreshScope 注释),然后使用它以编程方式选择所需的 HelloService 实现(例如使用 applicationContext.getBean() )?
  • 刷新范围不能改变加载哪个bean,它只能重新初始化一个bean
  • @spencergibb 好的,所以很明显这是在运行时根据配置更改替换 spring bean 实现的错误路径。这个 PoC 的目的是在运行时根据上下文变化选择业务逻辑。我想我将研究创建一个自定义Annotation 作为自定义Advice 的切入点,而Advice 反过来将使用ConfigurationProperties(上下文)在运行时选择不同的bean 方法。类似于ff4j 功能标志框架使用的@Flip 注释

标签: java spring spring-boot spring-cloud


【解决方案1】:

这个实现对我有用:

@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;
    }
  }

}

它的工作方式如下:

  1. 当第一次调用服务方法时,Spring 会创建 beanHelloDelegate,此时配置生效; bean 被放入刷新范围缓存中
  2. 因为@RefreshScope 每当配置更改时(country 属性,特别是在这种情况下)HelloDelegate bean 会从刷新范围缓存中清除
  3. 下一次调用发生时,Spring 必须再次创建 bean,因为它不存在于缓存中,因此使用新的 country 属性重复第 1 步

就我观察到这个实现的行为而言,Spring 会尽量避免重新创建 RefreshScope bean,如果它的配置没有改变的话。


当我发现这个问题时,我正在寻找更通用的解决方案来进行这种“运行时”实现替换。这种实现有一个明显的缺点:如果委托 bean 具有复杂的非同质配置(例如,每个 bean 都有自己的属性),代码就会变得很糟糕,因此不安全。

我使用这种方法来为工件提供额外的可测试性。这样 QA 就可以在存根和真正的集成之间切换,而无需付出很大的努力。我强烈建议避免将这种方法用于业务功能。

【讨论】:

    猜你喜欢
    • 2019-01-10
    • 1970-01-01
    • 2018-10-31
    • 1970-01-01
    • 2020-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多