在 ConfigurationProperties 更改后,使用 Spring @RefreshScope、@Conditional 批注在运行时替换 bean 注入 [英] Use Spring @RefreshScope, @Conditional annotations to replace bean injection at runtime after a ConfigurationProperties has changed

查看:128
本文介绍了在 ConfigurationProperties 更改后,使用 Spring @RefreshScope、@Conditional 批注在运行时替换 bean 注入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

}

它的工作方式如下:

  1. 当第一次调用服务方法时,Spring 会创建 bean HelloDelegate 并且配置在那一刻生效;bean 被放入刷新范围缓存
  2. 因为 @RefreshScope 每当配置改变时(country 属性,特别是在这种情况下)HelloDelegate bean 从刷新范围缓存中清除
  3. 当下次调用发生时,Spring 必须再次创建 bean,因为它不存在于缓存中,因此使用新的 country 属性重复步骤 1
  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
  2. Because of @RefreshScope whenever configuration is changed (country property particularly in this case) HelloDelegate bean gets cleared from refresh scope cache
  3. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆