基于服务可用性的弹簧自动接线



我需要根据 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名称(因此@Qualifiers)使用了相同的常量,所以大概 Spring 在计算这个@Configuration类的依赖图时无法分辨出区别......因为两个@Autowired 豆子似乎是同一件事(因为它们具有相同的名称)。

(在这种情况下使用@Qualifier还有一个次要原因,我不会在这里讨论,但足以说明可以拥有许多相同类型的地图。

一旦我更好地限定了名称,代码就完全按照我想要的方式做了,尽管以一种有点不优雅/丑陋的方式。

在某个时候,我会回去看看它是否看起来更优雅/不那么丑陋,并且使用@Conditional@Primary而不是 if/else 污垢同样有效。

这里的教训是,如果你显式命名 bean,绝对确保你的名字在你的应用程序中是唯一,即使你打算像这样交换东西。

最新更新