一、介紹
在實際的項目開發中,我們往往需要根據不同的環境做出不同的配置,例如:在開發環境下,我們會使用記憶體資料庫以便快速啟動服務并進行開發調試,在test環境、生産環境,會使用對應環境的資料庫。
如果我們的應用程式可以根據自身的環境做一些這樣的适配,那麼我們的程式開發無疑将更加靈活、高效。
在過去的應用程式開發中,我們常常會将這些環境變量寫在某個指定的配置檔案中,每次伺服器啟動的時候,會讀取伺服器中指定的配置檔案,進而實作根據不同的環境,應用程式能做出對應的适配。
但是這樣的工作,對于運維來說,非常苦逼,尤其是應用程式到達50個以上的時候,會非常不好維護,每次上線改配置,全靠人肉,想想都覺得反人類~
當我們在使用
SpringBoot
來開發應用程式的時候,這些工作量将大大簡化。
SpringBoot
為開發者提供了三種可選的條件裝配方式。
- Profile
- Conditional
- ConditionalOnProperty
下面,我們一起來了解一下具體的應用實踐。
二、程式實踐
2.1、Profile
SpringBoot 為應用程式提供了
Profile
這一概念,用來表示不同的環境。例如,我們分别定義開發、測試和生産這3個環境
- dev:開發環境
- test:測試環境
- production:生産環境
以上傳檔案為例,在開發環境下,我們将檔案上傳到本地,而在測試環境、生産環境,我們将檔案上傳到雲端服務商。
1、首先編寫兩套上傳服務
/**
* 上傳檔案到本地
* @since 2021-06-13
*/
public class FileUploader implements Uploader {
@Override
public String upload(File file) {
//上傳檔案到本地,并傳回絕對路徑
return null;
}
}
複制
/**
* 上傳檔案到OSS
* @since 2021-06-13
*/
public class OSSUploader implements Uploader {
@Override
public String upload(File file) {
//上傳檔案到雲端,并傳回絕對路徑
return null;
}
}
複制
2、然後編寫一個服務配置類,根據不同的環境,建立不同的實作類
@Configuration
public class AppConfig {
@Bean
@Profile("dev")
public Uploader initFileUploader() {
System.out.println("初始化一個上傳到本地的bean");
return new FileUploader();
}
@Bean
@Profile("!dev")
public Uploader initOSSUploader() {
System.out.println("初始化一個上傳到雲端的bean");
return new OSSUploader();
}
}
複制
3、最後,運作程式
在運作程式時,加上JVM參數
-Dspring.profiles.active=dev
就可以指定以
dev
環境啟動。
如果目前的
Profile
設定為
dev
,則
Spring
容器會調用
initFileUploader()
建立
FileUploader
,否則,調用
initOSSUploader()
建立
OSSUploader
。
注意:
@Profile("!dev")
表示非
dev
環境。
當然,你還可以在
application.properties
檔案中加上如下配置,一樣可以指定環境進行運作。
spring.profiles.active=dev
複制
2.2、Conditional
除了可以根據
@Profile
條件來決定是否建立某個
Bean
外,
Spring
還可以根據
@Conditional
決定是否建立某個
Bean
。
以發短信為例,在生産環境,我們會提供發短信服務,而在其他環境,我們不會向營運商發短信。
1、建立一個條件配置類
SMSEnvCondition
public class SMSEnvCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return "true".equalsIgnoreCase(context.getEnvironment().getProperty("enable.sms"));
}
}
複制
2、建立一個發短信的服務
@Component
@Conditional(SMSEnvCondition.class)
public class SendMessageService {
//...
}
複制
3、在
application.properties
檔案中,添加配置變量
enable.sms
enable.sms=true
複制
當
enable.sms
為
true
的時候,會建立
SendMessageService
對象,否則不建立。
2.3、ConditionalOnProperty
Spring
提供的條件裝配
@Conditional
,靈活性非常強,但是具體判斷邏輯還需要我們自己實作,比較麻煩。
實際上,
Spring Boot
為開發者提供了很多使用起來更簡單的條件注解,例如:
- ConditionalOnProperty:如果有指定的配置,條件生效
- ConditionalOnBean:如果有指定的Bean,條件生效
- ConditionalOnMissingBean:如果沒有指定的Bean,條件生效
- ConditionalOnMissingClass:如果沒有指定的Class,條件生效
- ConditionalOnWebApplication:在Web環境中條件生效
- ConditionalOnExpression:根據表達式判斷條件是否生效
我們以最常用的
@ConditionalOnProperty
注解為例,将上面的代碼改成如下方式即可實作按照條件進行加載。
@Component
@ConditionalOnProperty(name="enable.sms", havingValue="true")
public class SendMessageService {
//...
}
複制
當
enable.sms
的值等于
true
時,會執行個體化
SendMessageService
對象;反之,不會建立對象。
是不是超級簡單~~~
當然
@ConditionalOnProperty
的參數還不僅僅限于此,以上面上傳檔案為例,在開發環境,我們總是上傳到本地;在測試環境、生産環境,我們将檔案上傳到雲端,改造過程如下:
@Component
@ConditionalOnProperty(name = "file.storage", havingValue = "file", matchIfMissing = true)
public class FileUploader implements Uploader {
@Override
public String upload(File file) {
//上傳檔案到本地,并傳回絕對路徑
return null;
}
}
複制
@Component
@ConditionalOnProperty(name = "file.storage", havingValue = "oss")
public class OSSUploader implements Uploader {
@Override
public String upload(File file) {
//上傳檔案到雲端,并傳回絕對路徑
return null;
}
}
複制
當
file.storage
配置值為
file
,會加載
FileUploader
類;當
file.storage
配置值為
oss
,會加載
OSSUploader
類。
其中
@ConditionalOnProperty
中的
matchIfMissing
參數表示,當沒有找到對應配置參數時,會預設加載目前類,也就是
FileUploader
類。
三、小結
雖然,
@Profile
、
@Conditional
、
@ConditionalOnProperty
三個注解都能實作按照條件進行适配,但是
@Profile
注解控制比較粗糙,很難實作精細化控制。
在實際的使用過程中,使用最多的是
@Conditional
、
@ConditionalOnProperty
,可以很靈活的實作條件裝配。
其中,
@ConditionalOnProperty
是
@Conditional
的一種具體擴充實作,提供了很多非常實用的操作,在使用中,推薦大家使用
@ConditionalOnProperty
。
如果不夠,可以根據
@Conditional
條件裝配,編寫一套控制開關實作類。
四、參考
1、廖雪峰-使用條件裝配