我需要有条件地创建服务的三种可能实现之一,具体取决于Spring应用程序在运行时检测到的环境。如果Service A可用,那么我想创建一个使用Service A作为依赖项的具体实现类。如果服务A不可用,那么我想使用服务B作为依赖项创建一个实现。等等。
依赖于实现的类将自动装配接口,而不关心为特定环境选择的底层服务是什么。
我对此的第一个尝试是实现多个@Bean方法,这些方法返回bean或null,具体取决于Service是否可用,然后有一个单独的@Configuration类@Autowire(required = false)这两个可能的服务,有条件地创建实现,具体取决于哪个@Autowired字段不为null。
这里的问题是,当required = false时,Spring似乎并不关心它是否在等待构建候选者;也就是说,尝试选择实现的类可能在构造一个或两个required = false Beans之前构造,从而确保一个或两个可能总是为null,无论它是否可以设法正确初始化。
这种感觉就像我在这一点上反对,所以我正在寻找关于"对"的建议。做这种事情的方法,可以根据一些外部服务或环境的可用性来切换整套bean。
配置文件看起来不是正确答案,因为在我的服务bean尝试初始化我想要选择的实现之前,我不会知道;在创建上下文时,我当然不会知道它。
@Order也没有达到目标。 @Conditional也没有测试bean的存在(因为它仍然可能尚未构建)。与FactoryBean相同的问题 - 检查在FactoryBean被要求创建实例时可能尚未构造的bean的存在是没有用的。
我真正需要做的是根据其他bean的可用性创建一个Bean,但只有这些bean至少有机会尝试初始化。
答案 0 :(得分:0)
Spring Profiles是你的朋友。您可以通过环境变量,命令行参数和其他方法设置当前配置文件。您可以注释Spring管理的组件,以便为特定的配置文件创建它。
答案 1 :(得分:0)
在这种情况下,事实证明这是一个影响整个错误行为的切线错误。
为了给出一些背景知识,我的第一个,天真(但可行)的方法看起来像这样:
@Autowired(required=false)
@Qualifier(RedisConfig.HISTORY)
private RLocalCachedMap<String, History> redisHistoryMap;
@Autowired(required=false)
@Qualifier(HazelcastConfig.HISTORY)
private IMap<String, History> hazelcastHistoryMap;
// RequestHistory is an interface
@Bean
public RequestHistory requestHistory() {
if (redisHistoryMap != null) {
return new RedisClusteredHistory(redisHistoryMap);
} else if (hazelcastHistoryMap != null) {
return new HazelcastClusteredHistory(hazelcastHistoryMap);
} else {
return new LocalRequestHistory(); // dumb hashmap
}
}
在其他@Configuration
类中,如果此处获得@Autowired
的bean不可用(由于缺少配置,初始化期间的异常等),创建它们的@Bean方法将返回空。
观察到的行为是在调用@Bean
RLocalCachedMap<>
方法后调用此@Bean
方法,但在之前 Spring试图通过调用IMap<>
方法来创建@Bean
。我错误地认为这与required=false
有关,但实际上与它无关。
实际发生的事情是我偶然为两个@Bean
名称(以及@Qualifier
s)使用了相同的常量,所以可能Spring在计算其依赖图时无法区分它们对于这个@Configuration
类...因为两个@Autowire
d bean看起来是一样的(因为它们具有相同的名称)。
(在这种情况下使用@Qualifier
的次要原因是我不会进入这里,但只需说它可以有很多地图相同的类型。)
一旦我更好地对这些名字进行了限定,代码就完全符合我的要求,尽管它的方式有些不雅/丑陋。
在某些时候,我会回去看看它是否更优雅/更难看,并且使用@Conditional
和@Primary
代替if / else犯规也是如此。< / p>
这里的教训是,如果您明确命名bean,请确保您的应用程序中的名称 unique ,即使您打算像这样交换东西。