1. 程式人生 > >【Spring】使用@Profile註解實現開發、測試和生產環境的配置和切換,看完這篇我徹底會了!!

【Spring】使用@Profile註解實現開發、測試和生產環境的配置和切換,看完這篇我徹底會了!!

## 寫在前面 > 在實際的企業開發環境中,往往都會將環境分為:開發環境、測試環境和生產環境,而每個環境基本上都是互相隔離的,也就是說,開發環境、測試環境和生產環境是互不相通的。在以前的開發過程中,如果開發人員完成相應的功能模組並通過單元測試後,會通過手動修改配置檔案的形式,將專案的配置修改成測試環境,釋出到測試環境進行測試。測試通過後,再將配置修改為生產環境,釋出到生產環境。這樣手動修改配置的方式,一方面增加了開發和運維的工作量,而且總是手工修改各項配置檔案很容易出問題。那麼,有沒有什麼方式可以解決這些問題呢?答案是:有!通過@Profile註解就可以完全做到。 > > 關注 **冰河技術** 微信公眾號,回覆 “ **Spring註解** ” 關鍵字領取原始碼。 > > 如果文章對你有所幫助,歡迎大家留言、點贊、在看和轉發,大家的支援是我持續創作的動力! ## @Profile註解 在容器中如果存在同一型別的多個元件,也可以使用@Profile註解標識要獲取的是哪一個bean,這在不同的環境使用不同的變數的情景特別有用。例如,開發環境、測試環境、生產環境使用不同的資料來源,在不改變程式碼的情況下,可以使用這個註解來切換要連線的資料庫。 **步驟如下:** 1. 在bean上加@Profile註解,其value屬性值為環境標識,可以自定義 2. 使用無參構造方法建立容器 3. 設定容器環境,其值為第1步設定的環境標識 4. 設定容器的配置類 5. 重新整理容器 注:2、4、5步其實是帶參構造方法的步驟,相當於把帶參構造方法拆開,在其中插入一條語句設定容器環境,這些我們可以在Spring的原始碼中可以看出,比如下面的程式碼。 ```java public AnnotationConfigApplicationContext(Class... annotatedClasses) { this(); register(annotatedClasses); refresh(); } ``` 接下來,我們再來看下@Profile註解的原始碼,如下所示。 ```java package org.springframework.context.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.env.AbstractEnvironment; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Profiles; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(ProfileCondition.class) public @interface Profile { String[] value(); } ``` **注意:@Profile不僅可以標註在方法上,也可以標註在配置類上。如果標註在配置類上,只有在指定的環境時,整個配置類裡面的所有配置才會生效。如果一個bean上沒有使用@Profile註解進行標註,那麼這個bean在任何環境下都會被註冊到IOC容器中** ## 環境搭建 接下來,我們就一起來搭建使用@Profile註解實現開發、測試和生產環境的配置和切換的環境。這裡,我們以不同的資料來源為例。首先,我們在pom.xml檔案中新增c3p0和MySQL驅動的依賴,如下所示。 ```xml ``` 新增完專案依賴之後,我們在專案中新建ProfileConfig配置類,並在ProfileConfig配置類中模擬開發、測試、生產環境的資料來源,如下所示。 ```java package io.mykit.spring.plugins.register.config; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; /** * @author binghe * @version 1.0.0 * @description 測試多資料來源 */ @Configuration public class ProfileConfig { @Bean("devDataSource") public DataSource dataSourceDev() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_dev"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; } @Bean("testDataSource") public DataSource dataSourceTest() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_test"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; } @Bean("prodDataDource") public DataSource dataSourceProd() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_prod"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; } } ``` 這個類相對來說比較簡單,其中使用 @Bean("devDataSource")註解標註的是開發環境使用的資料來源;使用 @Bean("testDataSource")註解標註的是測試環境使用的資料來源;使用@Bean("prodDataDource")註解標註的是生產環境使用的資料來源。 接下來,我們建立ProfileTest類,並在ProfileTest類中新建一個testProfile01()方法來進行測試,如下所示。 ```java package io.mykit.spring.test; import io.mykit.spring.plugins.register.config.ProfileConfig; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import javax.sql.DataSource; import java.util.stream.Stream; /** * @author binghe * @version 1.0.0 * @description 測試類 */ public class ProfileTest { @Test public void testProfile01(){ //建立IOC容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProfileConfig.class); String[] names = context.getBeanNamesForType(DataSource.class); Stream.of(names).forEach(System.out::println); } } ``` 執行ProfileTest類的testProfile01()方法,輸出的結果資訊如下所示。 ```bash devDataSource testDataSource prodDataDource ``` 可以看到三種不同的資料來源成功註冊到了IOC容器中,說明我們的環境搭建成功了。 ## 根據環境註冊bean 我們成功搭建環境後,接下來,就是要實現根據不同的環境來向IOC容器中註冊相應的bean。也就是說,我們要實現在開發環境註冊開發環境下使用的資料來源;在測試環境註冊測試環境下使用的資料來源;在生產環境註冊生產環境下使用的資料來源。此時,@Profile註解就顯示出其強大的特性了。 我們在ProfileConfig類中為每個資料來源新增@Profile註解標識,如下所示。 ```java @Profile("dev") @Bean("devDataSource") public DataSource dataSourceDev() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_dev"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; } @Profile("test") @Bean("testDataSource") public DataSource dataSourceTest() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_test"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; } @Profile("prod") @Bean("prodDataDource") public DataSource dataSourceProd() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_prod"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; } ``` 我們使用@Profile("dev")註解來標識在開發環境下注冊devDataSource;使用@Profile("test")註解來標識在測試環境下注冊testDataSource;使用@Profile("prod")註解來標識在生產環境下注冊prodDataDource。 此時,我們執行ProfileTest類的testProfile01()方法,**發現命令列並未輸出結果資訊**。說明我們為不同的資料來源新增@Profile註解後,預設是不會向IOC容器中註冊bean的,需要我們根據環境顯示指定向IOC容器中註冊相應的bean。 **換句話說:通過@Profile註解加了環境標識的bean,只有這個環境被啟用的時候,相應的bean才會被註冊到IOC容器中。** **如果我們需要一個預設的環境怎麼辦呢?** 此時,我們可以通過@Profile("default")註解來標識一個預設的環境,例如,我們將devDataSource環境標識為預設環境,如下所示。 ```java @Profile("default") @Bean("devDataSource") public DataSource dataSourceDev() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_dev"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; } ``` 此時,我們執行ProfileTest類的testProfile01()方法,輸出的結果資訊如下所示。 ```bash devDataSource ``` 可以看到,我們在devDataSource資料來源上使用@Profile("default")註解將其設定為預設的資料來源,執行測試方法時命令列會輸出devDataSource。 接下來,我們將devDataSource資料來源的@Profile("default")註解還原成@Profile("dev")註解,標識它為一個開發環境下注冊的資料來源。 **那麼,我們如何根據不同的環境來註冊相應的bean呢?** 第一種方式就是根據命令列引數來確定環境,我們在執行程式的時候可以新增相應的命令列引數,例如,我們現在的環境是測試環境,那可以在執行程式的時候新增如下命令列引數。 ```bash -Dspring.profiles.active=test ``` 第二種方式就是通過AnnotationConfigApplicationContext類的無參構造方法來實現。我們在程式中呼叫AnnotationConfigApplicationContext的無參構造方法來生成IOC容器,在容器進行初始化之前,我們就為IOC容器設定相應的環境,然後再為IOC容器設定主配置類。例如,我們將IOC容器設定為生產環境,如下所示。 ```java @Test public void testProfile02(){ //建立IOC容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.getEnvironment().setActiveProfiles("prod"); context.register(ProfileConfig.class); context.refresh(); String[] names = context.getBeanNamesForType(DataSource.class); Stream.of(names).forEach(System.out::println); } ``` 此時,我們執行testProfile02()方法,輸出的結果資訊如下所示。 ```bash prodDataDource ``` 可以看到,命令列輸出了prodDataDource,說明我們成功將IOC環境設定為了生產環境。 @Profile不僅可以標註在方法上,也可以標註在配置類上。如果標註在配置類上,只有在指定的環境時,整個配置類裡面的所有配置才會生效。例如,我們在ProfileConfig類上標註@Profile("dev")註解,如下所示。 ```java @Profile("dev") @Configuration public class ProfileConfig { /*********程式碼省略*********/ } ``` 接下來,我們執行testProfile02()方法,發現命令列中未輸出任何資訊。 **這是因為我們在testProfile02()方法中指定了當前的環境為生產環境,而ProfileConfig類上標註的註解為@Profile("dev"),說明ProfileConfig類中的所有配置只有在開發環境下才會生效。所以,此時沒有任何資料來源註冊到IOC容器中,命令列不會列印任何資訊。** ## 重磅福利 關注「 **冰河技術** 」微信公眾號,後臺回覆 “**設計模式**” 關鍵字領取《**深入淺出Java 23種設計模式**》PDF文件。回覆“**Java8**”關鍵字領取《**Java8新特性教程**》PDF文件。回覆“**限流**”關鍵字獲取《**億級流量下的分散式限流解決方案**》PDF文件,三本PDF均是由冰河原創並整理的超硬核教程,面試必備!! **好了,今天就聊到這兒吧!別忘了點個贊,給個在看和轉發,讓更多的人看到,一起學習,一起進步!!** ## 寫在最後 > 如果你覺得冰河寫的還不錯,請微信搜尋並關注「 **冰河技術** 」微信公眾號,跟冰河學習高併發、分散式、微服務、大資料、網際網路和雲原生技術,「 **冰河技術** 」微信公眾號更新了大量技術專題,每一篇技術文章乾貨滿滿!不少讀者已經通過閱讀「 **冰河技術** 」微信公眾號文章,吊打面試官,成功跳槽到大廠;也有不少讀者實現了技術上的飛躍,成為公司的技術骨幹!如果你也想像他們一樣提升自己的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 **冰河技術** 」微信公眾號吧,每天更新超硬核技術乾貨,讓你對如何提升技術能力不再迷茫! ![](https://img-blog.csdnimg.cn/20200716220443647.png#pic