我需要根据 Spring 应用程序在运行时检测到的环境,有条件地创建服务的三种可能实现之一。 如果服务 A 可用,那么我想创建一个使用服务 A 作为依赖项的具体实现类。 如果服务 A 不可用,那么我想使用服务 B 作为依赖项创建一个实现。 等等。
依赖于实现的类将自动连接接口,而不关心为特定环境选择的基础服务是什么。
我在这方面的第一个尝试是实现多个 @Bean 方法,这些方法要么返回 bean 要么返回 null,具体取决于服务是否可用,然后有一个单独的 @Configuration 类来@Autowire(required=false)两个可能的服务,根据@Autowired字段中的哪些不是 null 有条件地创建实现。
这里的问题是,当 required=false 时,Spring 似乎并不关心它是否等待构建候选者;也就是说,尝试选择实现的类可能会在一个或两个 required=false Beans 被构造之前被构造,从而确保一个或两个可能始终为 null, 无论它是否可以设法正确初始化。
在这一点上,感觉就像我在违背原则,所以我正在寻找关于做这种事情的"正确"方法的建议,其中整套豆子可能会根据某些外部服务或环境的可用性被切换。
配置文件看起来不像正确的答案,因为直到我的服务bean尝试初始化我想要选择的实现之后,我才会知道;在我创建上下文时,我当然不会知道它。
@Order也没有实现目标。 @Conditional也没有对 bean 的存在进行测试(因为它可能还没有被构建)。 与 FactoryBean 相同的问题 - 在要求 FactoryBean 创建实例时检查可能尚未构建的 bean 是否存在是没有好处的。
我真正需要做的是根据其他 bean 的可用性创建一个 Bean,但前提是这些 bean 至少有机会尝试初始化。
Spring Profiles是你的朋友。您可以通过环境变量、命令行参数和其他方法设置当前配置文件。您可以注释 Spring 管理的组件,以便为某个配置文件创建它。
春季文档中的弹簧配置文件
在这种情况下,事实证明这是一个切线错误,影响了整个错误行为。
为了提供一些背景,我的第一个天真(但可行)的方法如下所示:
@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方法将返回 null。
观察到的行为是,在调用RLocalCachedMap<>
@Bean
方法之后,但在 Spring 尝试通过调用其@Bean
方法创建IMap<>
之前,调用此@Bean
方法。 我错误地认为这与required=false
有关,但实际上这与它无关。
实际发生的事情是我不小心对两个@Bean
名称(因此@Qualifier
s)使用了相同的常量,所以大概 Spring 在计算这个@Configuration
类的依赖图时无法分辨出区别......因为两个@Autowire
d 豆子似乎是同一件事(因为它们具有相同的名称)。
(在这种情况下使用@Qualifier
还有一个次要原因,我不会在这里讨论,但足以说明可以拥有许多相同类型的地图。
一旦我更好地限定了名称,代码就完全按照我想要的方式做了,尽管以一种有点不优雅/丑陋的方式。
在某个时候,我会回去看看它是否看起来更优雅/不那么丑陋,并且使用@Conditional
和@Primary
而不是 if/else 污垢同样有效。
这里的教训是,如果你显式命名 bean,绝对确保你的名字在你的应用程序中是唯一的,即使你打算像这样交换东西。