在运行时更改 Spring beans 实现

Changing the Spring beans implementation at runtime

我有一个接口和多个实现。我正在自动连接 类 中的接口以供使用。我需要在运行时选择不同的实现。

public class Util {
  public void getClient();
}

实施

public class UtilOne implements Util {
  public void getClient() {...}
}

public class UtilTwo implements Util {
  public void getClient() {...}
}

@Configuration
public class AppConfig {
  
  @Autowired
  @Bean
  @Primary
  public Util utilOne() {
    return new UtilOne();
  }

  @Autowired
  @Bean
  public Util utilTwo() {
    return new UtilTwo();
  }

}
@Component
public class DemoService {

  @Autowired
  private Util util;
}

出于某种原因,如果我们无法在 UtilOne 中获取客户端,我想在不重新启动应用程序的情况下切换到 UtilTwo。我想将 DemoService 中的 Util 对象更改为 UtilTwo 对象。 属性 active.util 将来自数据库,我们可以从 UI 更新吗?

这种方式行不通 - 如果您将 Util 的某个实现连接到,比如说,class SampleClass(这是一个单例),您就无法真正将 Util 的实现更改为无需重新启动应用程序上下文即可有所不同。

因此,我建议采用另一种方法,而不是采用这种方式。您说在运行时评估的某些条件下您想要切换实现。这是一种什么样的状态?是否可以提取这个条件判定逻辑?

如果是这样,您可以自动装配一个特殊的 DynamicUtil,它将保存对所有实用程序的引用,并将根据条件调用所需的实用程序:

// represents all possible business 'runtime' outcomes
enum ConditionOutcome {
   A, B, C 
}
interface ConditionEvaluator {
   ConditionOutcome evaluate(); // when called in runtime will evaluate a condition that currently exists in the system
}

interface Util {
   void foo();
   ConditionOutcome relevantOfOutcome();
}

class Utill1Impl implements Util {
   public void foo() {...}
   public ConditionOutcome relevantOfOutcome() {return ConditionOutcome.A;}
}
class Utill2Impl implements Util {
   public void foo() {...}
   public ConditionOutcome relevantOfOutcome() {return ConditionOutcome.B;}
}
class Utill3Impl implements Util {
   public void foo() {...}
   public ConditionOutcome relevantOfOutcome() {return ConditionOutcome.C;}
}

class DynamicUtil {
   private final Map<ConditionOutcome, Util> possibleImpls;
   private final ConditionEvaluator evaluator;

   public class DynamicUtil(List<Util> allImplementations, ConditionEvaluator evaluator) {
      // create a map by calling the 'relevantOfOutcome' per util impl in a loop
    this.evaluator = evaluator;
   }
   
   public void foo() {
      ConditionOutcome key = evaluator.evaluate();
      // pick the relevant implementation based on evaluated key
      possibleImpls.get(key).foo();
   }
}

现在有了这样的设计,您可以动态添加新的可能结果(以及应该实现它们的实用程序。您 class 系统中的人将不得不自动装配 DynamicUtil 不过,这样有效地您'将引入一个额外的间接级别,但会获得灵活性

class SampleClass { // a business class that will need to work with util capable of being changed during the runtime 
   @Autowired
   private DynamicUtil util;
   ... 
}

您可以尝试使用 委托代理 的方法。有一个主要的 Util bean,它只是实际实现的包装器,并允许在运行时更改其内部委托。此外,您可以创建类似 manager/helper class 的内容,其中包含对所有实际实现 bean 的引用,以简化它们之间的切换。

@Component
@Primary
public class DelegatingUtil implements Util {
  private Util delegate;

  public void setDelegate(Util delegate){ this.delegate = delegate; }
  public Util getDelegate(){ return delegate; }

  public void getClient() {
    return delegate.getClient();
  }
}

切换逻辑适用的地方:

  // Use @Named or @Qualifier or any other way to obtain references to actual implementations 
  private Util defaultImpl;
  private Util fallbackImpl;

  @Autowired
  private DelegatingUtil switcher;

  public void switchToFallback(){
    this.switcher.setDelegate(this.fallbackImpl);
  }

注意,这只是示意图示例,您应该注意 bean 创建顺序、使用限定符注入(可能是有条件的)、初始化等细节。

根据您的情况,这是一个简单的方法。主要思想是通过 PropertyService 从数据库读取 active.util 属性 并将你的 Utils 包装到 RouteUtil:

@Component
public class RouteUtil {

    @Autowired
    private PropertyService propertyService;  

    @Qualifier("one")
    @Autowired
    private Util utilOne;

    @Qualifier("two")
    @Autowired
    private Util utilTwo;

    public void getClient() {
        if ("one".equals(propertyService.read("active.util"))) {
            utilOne.getClient();
        } else {
            utilTwo.getClient();
        }
    }
}

在演示服务中:

@Service
public class DemoService {

  @Autowired
  private RouteUtil util;

  // RouteUtil.getClient() ...
}

您可以将 active.util 更改为 select,无需重新启动应用程序即可在运行时使用该 Util。

Spring 为您提供了一个我个人不喜欢的解决方案。你可以做的是声明一个

@MyInterface
List<MyIntercase> myimpls

其中 MyInterface 是您的接口,列表将包含所有实现。但是,我(因为我不喜欢这个解决方案)编写了我自己的解决方案,您可以在其中拥有一个所有实现都为 self-populated 的静态工厂。因此,您不必注入所有实现,而是通过 class 名称或 custom-defined 名称从工厂 run-time 选择它们。另一个优点是每个工厂的 custom-defined 名称必须是唯一的。因此,假设您有一些分阶段的过程,并且对于每个阶段,您都有自己的界面和自己的工厂。因此,您可以为不同接口的实现使用相同的自定义名称。假设您使用文本格式 XML、JSON 和 CSV,并且有一个用于 stage-1 stage-2 stage-3 的接口(和相关工厂)。所以对于每个 stage-X 接口,你可以有名为 JSONXMLCSV 的实现,所以你所要做的就是有一个名为 currentType 的变量,它将保存其中一个值 - JSONXMLCSV,对于每个阶段,您可以使用工厂来获得适当的实现:

Stage1Handler handler = stage-1-factory.getInstance(currentValue);
Stage2Handler handler = stage-2-factory.getInstance(currentValue);
Stage3Handler handler = stage-3-factory.getInstance(currentValue);

其中 Stage[X]Handler 是您的界面。但这只是一个额外的好处。 Open-source MgntUtils 库中提供了我的解决方案。有关此特定装置的文章可在此处找到:Non-intrusive access to "Orphaned" Beans in Spring framework Also, I describe this feature in my library javadoc here. The library could be found as Maven artifact and on Github 包括源代码和 Javadoc