在运行时更改 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 接口,你可以有名为 JSON
、XML
和 CSV
的实现,所以你所要做的就是有一个名为 currentType
的变量,它将保存其中一个值 - JSON
、XML
和 CSV
,对于每个阶段,您可以使用工厂来获得适当的实现:
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
我有一个接口和多个实现。我正在自动连接 类 中的接口以供使用。我需要在运行时选择不同的实现。
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 接口,你可以有名为 JSON
、XML
和 CSV
的实现,所以你所要做的就是有一个名为 currentType
的变量,它将保存其中一个值 - JSON
、XML
和 CSV
,对于每个阶段,您可以使用工厂来获得适当的实现:
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