寫在前面
在實際的企業開發環境中,往往都會將環境分為:開發環境、測試環境和生產環境,而每個環境基本上都是互相隔離的,也就是說,開發環境、測試環境和生產環境是互不相通的。在以前的開發過程中,如果開發人員完成相應的功能模塊並通過單元測試后,會通過手動修改配置文件的形式,將項目的配置修改成測試環境,發布到測試環境進行測試。測試通過后,再將配置修改為生產環境,發布到生產環境。這樣手動修改配置的方式,一方面增加了開發和運維的工作量,而且總是手工修改各項配置文件很容易出問題。那么,有沒有什么方式可以解決這些問題呢?答案是:有!通過@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均是由冰河原創並整理的超硬核教程,面試必備!!
好了,今天就聊到這兒吧!別忘了點個贊,給個在看和轉發,讓更多的人看到,一起學習,一起進步!!
寫在最后
如果你覺得冰河寫的還不錯,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習高並發、分布式、微服務、大數據、互聯網和雲原生技術,「 冰河技術 」微信公眾號更新了大量技術專題,每一篇技術文章干貨滿滿!不少讀者已經通過閱讀「 冰河技術 」微信公眾號文章,吊打面試官,成功跳槽到大廠;也有不少讀者實現了技術上的飛躍,成為公司的技術骨干!如果你也想像他們一樣提升自己的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公眾號吧,每天更新超硬核技術干貨,讓你對如何提升技術能力不再迷茫!