在項目開發過程中,有這樣的一種常見的場景,比如根據環境的不同,發短信的服務也是不同的,但是依賴短信服務的系統調用的都是相同的接口,這樣就需要針對環境來做區分,調用不同的短信通道接口。舉例的這種情況,你可能會想到使用策略模式的方式來實現,當然這也是可以的。
但是只有兩種策略而且在一個環境當中,只會有一種固定策略可以生效的情況下,使用策略模式未免過於麻煩,本文尤其適合私有化項目部署,會根據私有化環境執行不同策略的情況。
本文首發於個人博客: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
