Spring 中的 Fallback Bean

1、概览

本文将带你了解 Spring 中 Fallback Bean 的概念。

Fallback Bean 是在 Spring Framework 6.2.0-M1 中引入的,当另一个相同类型的 Bean 不可用或无法初始化时,它提供了一种替代实现。

2、Primary Bean 和 Fallback Bean

在 Spring 中,我们可以定义多个相同类型的 Bean。默认情况下,Spring 使用 Bean 名称和类型来标识 Bean。当有多个名称和类型相同的 Bean 时,可以使用 @Primary 注解将其中一个标记为 Primary(主要)Bean,使其优先于其他 Bean。如果在 Application Context 初始化时创建了多个相同类型的 Bean,而我们又想指定默认使用哪个 Bean,那么这就非常有用了。

同样,我们可以定义一个 Fallback Bean,以便在没有其他合格 Bean 时提供替代实现。我们可以使用 @Fallback 注解将一个 Bean 标记为 Fallback(后备) Bean。只有当没有其他同名的 Bean 可用时,才会将后备 Bean 注入到 Application Context。

3、示例代码

来看一个示例,演示如何在 Spring 应用中使用 Primary Bean 和 Fallback Bean。

我们要创建一个使用不同 MQ 服务发送消息的小应用。假设我们在生产环境和非生产环境中拥有多个 MQ 服务,并且需要在它们之间切换以优化性能和成本。

3.1、Messaging 接口

首先,为服务定义一个接口:

public interface MessagingService {
    void sendMessage(String text);
}

该接口有一个发送文本消息的方法。

3.2、Primary Bean

接下来,实现 MessagingService 的 Primary Bean 实现:

@Service
@Profile("production")
@Primary
public class ProductionMessagingService implements MessagingService {
    @Override
    public void sendMessage(String text) {
       // 生产环境中的实现
    }
}

如上,我们使用 @Profile 注解来指定此 Bean 仅在 production profile 激活时可用。我们还使用 @Primary 注解将其标记为 Primary Bean。

3.3、非 Primary Bean

定义另一个 MessagingService 接口的实现,非 Primary Bean:

@Service
@Profile("!test")
public class DevelopmentMessagingService implements MessagingService {
    @Override
    public void sendMessage(String text) {
        // 开发环境下的实现
    }
}

在此实现中,我们使用 @Profile 注解来指定该 Bean 在 test Profile 未激活时可用。这意味着除了 test Profile 外,它在所有 Profile 中都是可用的。

3.4、Fallback Bean

最后,为 MessagingService 定义一个 Fallback(备选) Bean:

@Service
@Fallback
public class FallbackMessagingService implements MessagingService {
    @Override
    public void sendMessage(String text) {
        // fallback 实现
    }
}

在此实现中,我们使用 @Fallback 注解将此 Bean 标记为 Fallback Bean。只有当没有其他相同类型的 Bean 可用时,才会注入此 Bean。

4、测试

现在,让我们通过自动装配 MessagingService 并根据 Profile 检查使用的实现来测试我们的应用。

4.1、未激活 Profile

第一个测试,不激活任何 Profile。由于 production Profile 未激活,ProductionMessagingService 不可用,而其他两个 Bean 可用。

此时的 MessagingService 实现应该是 DevelopmentMessagingService,因为它优先于 Fallback Bean:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {FallbackMessagingService.class, DevelopmentMessagingService.class, ProductionMessagingService.class})
public class DevelopmentMessagingServiceUnitTest {
    @Autowired
    private MessagingService messagingService;

    @Test
    public void givenNoProfile_whenSendMessage_thenDevelopmentMessagingService() {
        assertEquals(messagingService.getClass(), DevelopmentMessagingService.class);
    }
}

4.2、激活 production Profile

接下来,激活 production Profile。现在,ProductionMessagingService 和另外两个 Bean 都可用。

在此测试中 MessagingService 的实现应该是 ProductionMessagingService,因为它被标记为 Primary Bean:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {FallbackMessagingService.class, DevelopmentMessagingService.class, ProductionMessagingService.class})
@ActiveProfiles("production")
public class ProductionMessagingServiceUnitTest {
    @Autowired
    private MessagingService messagingService;

    @Test
    public void givenProductionProfile_whenSendMessage_thenProductionMessagingService() {
        assertEquals(messagingService.getClass(), ProductionMessagingService.class);
    }
}

4.3、激活 test Profile

最后,激活 test Profile。这将从 Application Context 中移除 DevelopmentMessagingService Bean。由于未激活 production profile,ProductionMessagingService 也不可用。

此时 MessagingService 的实现应该是 FallbackMessagingService,因为它是唯一可用的 Bean:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {FallbackMessagingService.class, DevelopmentMessagingService.class, ProductionMessagingService.class})
@ActiveProfiles("test")
public class FallbackMessagingServiceUnitTest {
    @Autowired
    private MessagingService messagingService;

    @Test
    public void givenTestProfile_whenSendMessage_thenFallbackMessagingService() {
        assertEquals(messagingService.getClass(), FallbackMessagingService.class);
    }
}

5、总结

本文介绍了 Spring 中的 Fallback Bean 概念,通过示例介绍了如何定义 Primary Bean 和 Fallback Bean 以及如何在 Spring 应用中使用它们。

当任何其他符合条件的 Bean 不可用时,Fallback Bean 提供了一种替代实现。这在根据激活的 profile 或其他条件在不同实现之间切换时非常有用。


Ref:https://www.baeldung.com/spring-fallback-beans