利用Spring的@ConditionXXX注解實現策略模式


在項目開發過程中,有這樣的一種常見的場景,比如根據環境的不同,發短信的服務也是不同的,但是依賴短信服務的系統調用的都是相同的接口,這樣就需要針對環境來做區分,調用不同的短信通道接口。舉例的這種情況,你可能會想到使用策略模式的方式來實現,當然這也是可以的。

但是只有兩種策略而且在一個環境當中,只會有一種固定策略可以生效的情況下,使用策略模式未免過於麻煩,本文尤其適合私有化項目部署,會根據私有化環境執行不同策略的情況。

本文首發於個人博客:http://nullpointer.pw/spring-condition-annotation.html

應用場景

本文講解如何使用@ConditionXXX 系列注解來更方便地實現這種策略選擇的場景。

@ConditionXXX 系列注解是 Spring 提供的一組注解,下面表格列出所有的注解以及對應的作用,常用的注解通過☆標注

上文中的例子中,多個策略同一個環境其實只需要加載一個適合的策略即可,完全沒有必要加載用不上的其他策略,在系統啟動時,只取所需的策略就足矣,實現起來相較於策略模式方便不是一點點。

常用注解

注解 作用 常用
@ConditionalOnProperty 當指定的屬性有指定的值時進行實例化
@ConditionalOnMissingBean 當容器里沒有指定 Bean 的條件下進行實例化。
@ConditionalOnExpression 基於 SpEL 表達式的條件判斷,多條件
@ConditionalOnBean 僅僅在當前上下文中存在某個對象時,才會實例化一個 Bean。

以不同環境發送短信為例,進行代碼實現 (當然這個例子有點強上的異味,不過用於來解釋這幾個主鍵的功用卻是很合適,具體應用場景請觸類旁通)。

示例實現

首先會定義一個用於發送短信的接口,因為會有多種環境,如開發環境、測試環境、生產環境,所以會有相應環境的實現類。

public interface SmsService {
    Boolean send(String mobile, String content);
}

定義多環境配置類

- application.yml
- application-dev.yml
- application-prod.yml
- application-test.yml

application-xxx.yml 中分別指定配置,如 application-dev.yml

custom:
  env: dev

實現每個環境的具體短信發送實現類,使用到了 @ConditionalOnProperty(name = {"custom.env"}, havingValue = "test") ,含義就是讀取配置文件中 key 為 custom.env 的配置,如果值為 havingValue 屬性所指定的值 test,則會實例化這個 Bean,否則不進行實例化。

以下其他環境的同理,不再贅述。

/**
 * 測試環境短信通道
 *
 * @author WeJan
 * @since 2020-04-28
 */
@Service
@ConditionalOnProperty(name = {"custom.env"}, havingValue = "test")
public class TestSmsServiceImpl implements SmsService {
    @Override
    public Boolean send(String mobile, String content) {
        System.out.println("短信發送成功, by TestSmsServiceImpl");
        return true;
    }
}
/**
 * 開發環境短信通道
 *
 * @author WeJan
 * @since 2020-04-28
 */
@Service
@ConditionalOnProperty(name = {"custom.env"}, havingValue = "dev")
public class DevSmsServiceImpl implements SmsService {

    @Override
    public Boolean send(String mobile, String content) {
        System.out.println("短信發送成功, by DevSmsServiceImpl");
        return true;
    }
}
/**
 * 生產環境短信通道
 *
 * @author WeJan
 * @since 2020-04-28
 */
@Service
@ConditionalOnProperty(name = {"custom.env"}, havingValue = "prod")
public class ProdSmsServiceImpl implements SmsService {

    @Override
    public Boolean send(String mobile, String content) {
        System.out.println("短信發送成功, by ProdSmsServiceImpl");
        return true;
    }
}

正常情況下,使用 @ConditionalOnProperty 便足以滿足大部分的需求,但還是有例外情況,需要設置兜底策略,可以通過 @ConditionalOnMissingBean 來實現,該注解 value 屬性中對應的類若都未曾實例化,則實例化當前的 Bean。

**
 * 默認短信通道
 *
 * @author WeJan
 * @since 2020-04-28
 */
@Service
@ConditionalOnMissingBean(value = {DevSmsServiceImpl.class, TestSmsServiceImpl.class, ProdSmsServiceImpl.class})
public class DefaultSmsServiceImpl implements SmsService {

    @Override
    public Boolean send(String mobile, String content) {
        System.out.println("短信發送成功, by DefaultSmsServiceImpl");
        return true;
    }
}

測試一番

為了方便測試,使用 @ActiveProfiles 注解指定當前測試使用的配置環境,

@RunWith(SpringRunner.class)
//@ActiveProfiles(value = "prod")
//@ActiveProfiles(value = "dev")
@ActiveProfiles(value = "test")
@SpringBootTest(classes = SpringApp.class)
public class SmsServiceTest {
    @Resource
    private SmsService smsService;
    @Test
    public void testSend() {
        smsService.send("13333333333", "Test");
    }
}

測試當激活環境為 dev 時,控制台輸出,其他環境不再演示,讀者可自行嘗試。

短信發送成功, by DevSmsServiceImpl


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM