寫在前面
在實際的企業開發環境中,往往都會將環境分為:開發環境、測試環境和生產環境,而每個環境基本上都是互相隔離的,也就是說,開發環境、測試環境和生產環境是互不相通的。在以前的開發過程中,如果開發人員完成相應的功能模組並通過單元測試後,會通過手動修改配置檔案的形式,將專案的配置修改成測試環境,釋出到測試環境進行測試。測試通過後,再將配置修改為生產環境,釋出到生產環境。這樣手動修改配置的方式,一方面增加了開發和運維的工作量,而且總是手工修改各項配置檔案很容易出問題。那麼,有沒有什麼方式可以解決這些問題呢?答案是:有!通過@Profile註解就可以完全做到。
關注 冰河技術 微信公眾號,回覆 “ Spring註解 ” 關鍵字領取原始碼。
如果文章對你有所幫助,歡迎大家留言、點贊、在看和轉發,大家的支援是我持續創作的動力!
@Profile註解
在容器中如果存在同一型別的多個元件,也可以使用@Profile註解標識要獲取的是哪一個bean,這在不同的環境使用不同的變數的情景特別有用。例如,開發環境、測試環境、生產環境使用不同的資料來源,在不改變程式碼的情況下,可以使用這個註解來切換要連線的資料庫。
步驟如下:
- 在bean上加@Profile註解,其value屬性值為環境標識,可以自定義
- 使用無參構造方法建立容器
- 設定容器環境,其值為第1步設定的環境標識
- 設定容器的配置類
- 重新整理容器
注:2、4、5步其實是帶參構造方法的步驟,相當於把帶參構造方法拆開,在其中插入一條語句設定容器環境,這些我們可以在Spring的原始碼中可以看出,比如下面的程式碼。
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}
接下來,我們再來看下@Profile註解的原始碼,如下所示。
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驅動的依賴,如下所示。
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
新增完專案依賴之後,我們在專案中新建ProfileConfig配置類,並在ProfileConfig配置類中模擬開發、測試、生產環境的資料來源,如下所示。
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()方法來進行測試,如下所示。
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()方法,輸出的結果資訊如下所示。
devDataSource
testDataSource
prodDataDource
可以看到三種不同的資料來源成功註冊到了IOC容器中,說明我們的環境搭建成功了。
根據環境註冊bean
我們成功搭建環境後,接下來,就是要實現根據不同的環境來向IOC容器中註冊相應的bean。也就是說,我們要實現在開發環境註冊開發環境下使用的資料來源;在測試環境註冊測試環境下使用的資料來源;在生產環境註冊生產環境下使用的資料來源。此時,@Profile註解就顯示出其強大的特性了。
我們在ProfileConfig類中為每個資料來源新增@Profile註解標識,如下所示。
@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環境標識為預設環境,如下所示。
@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()方法,輸出的結果資訊如下所示。
devDataSource
可以看到,我們在devDataSource資料來源上使用@Profile("default")註解將其設定為預設的資料來源,執行測試方法時命令列會輸出devDataSource。
接下來,我們將devDataSource資料來源的@Profile("default")註解還原成@Profile("dev")註解,標識它為一個開發環境下注冊的資料來源。
那麼,我們如何根據不同的環境來註冊相應的bean呢?
第一種方式就是根據命令列引數來確定環境,我們在執行程式的時候可以新增相應的命令列引數,例如,我們現在的環境是測試環境,那可以在執行程式的時候新增如下命令列引數。
-Dspring.profiles.active=test
第二種方式就是通過AnnotationConfigApplicationContext類的無參構造方法來實現。我們在程式中呼叫AnnotationConfigApplicationContext的無參構造方法來生成IOC容器,在容器進行初始化之前,我們就為IOC容器設定相應的環境,然後再為IOC容器設定主配置類。例如,我們將IOC容器設定為生產環境,如下所示。
@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()方法,輸出的結果資訊如下所示。
prodDataDource
可以看到,命令列輸出了prodDataDource,說明我們成功將IOC環境設定為了生產環境。
@Profile不僅可以標註在方法上,也可以標註在配置類上。如果標註在配置類上,只有在指定的環境時,整個配置類裡面的所有配置才會生效。例如,我們在ProfileConfig類上標註@Profile("dev")註解,如下所示。
@Profile("dev")
@Configuration
public class ProfileConfig {
/*********程式碼省略*********/
}
接下來,我們執行testProfile02()方法,發現命令列中未輸出任何資訊。
這是因為我們在testProfile02()方法中指定了當前的環境為生產環境,而ProfileConfig類上標註的註解為@Profile("dev"),說明ProfileConfig類中的所有配置只有在開發環境下才會生效。所以,此時沒有任何資料來源註冊到IOC容器中,命令列不會列印任何資訊。
重磅福利
關注「 冰河技術 」微信公眾號,後臺回覆 “設計模式” 關鍵字領取《深入淺出Java 23種設計模式》PDF文件。回覆“Java8”關鍵字領取《Java8新特性教程》PDF文件。回覆“限流”關鍵字獲取《億級流量下的分散式限流解決方案》PDF文件,三本PDF均是由冰河原創並整理的超硬核教程,面試必備!!
好了,今天就聊到這兒吧!別忘了點個贊,給個在看和轉發,讓更多的人看到,一起學習,一起進步!!
寫在最後
如果你覺得冰河寫的還不錯,請微信搜尋並關注「 冰河技術 」微信公眾號,跟冰河學習高併發、分散式、微服務、大資料、網際網路和雲原生技術,「 冰河技術 」微信公眾號更新了大量技術專題,每一篇技術文章乾貨滿滿!不少讀者已經通過閱讀「 冰河技術 」微信公眾號文章,吊打面試官,成功跳槽到大廠;也有不少讀者實現了技術上的飛躍,成為公司的技術骨幹!如果你也想像他們一樣提升自己的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公眾號吧,每天更新超硬核技術乾貨,讓你對如何提升技術能力不再迷茫!