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

冰河團隊發表於2020-08-24

寫在前面

在實際的企業開發環境中,往往都會將環境分為:開發環境、測試環境和生產環境,而每個環境基本上都是互相隔離的,也就是說,開發環境、測試環境和生產環境是互不相通的。在以前的開發過程中,如果開發人員完成相應的功能模組並通過單元測試後,會通過手動修改配置檔案的形式,將專案的配置修改成測試環境,釋出到測試環境進行測試。測試通過後,再將配置修改為生產環境,釋出到生產環境。這樣手動修改配置的方式,一方面增加了開發和運維的工作量,而且總是手工修改各項配置檔案很容易出問題。那麼,有沒有什麼方式可以解決這些問題呢?答案是:有!通過@Profile註解就可以完全做到。

關注 冰河技術 微信公眾號,回覆 “ Spring註解 ” 關鍵字領取原始碼。

如果文章對你有所幫助,歡迎大家留言、點贊、在看和轉發,大家的支援是我持續創作的動力!

@Profile註解

在容器中如果存在同一型別的多個元件,也可以使用@Profile註解標識要獲取的是哪一個bean,這在不同的環境使用不同的變數的情景特別有用。例如,開發環境、測試環境、生產環境使用不同的資料來源,在不改變程式碼的情況下,可以使用這個註解來切換要連線的資料庫。

步驟如下:

  1. 在bean上加@Profile註解,其value屬性值為環境標識,可以自定義
  2. 使用無參構造方法建立容器
  3. 設定容器環境,其值為第1步設定的環境標識
  4. 設定容器的配置類
  5. 重新整理容器

注: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均是由冰河原創並整理的超硬核教程,面試必備!!

好了,今天就聊到這兒吧!別忘了點個贊,給個在看和轉發,讓更多的人看到,一起學習,一起進步!!

寫在最後

如果你覺得冰河寫的還不錯,請微信搜尋並關注「 冰河技術 」微信公眾號,跟冰河學習高併發、分散式、微服務、大資料、網際網路和雲原生技術,「 冰河技術 」微信公眾號更新了大量技術專題,每一篇技術文章乾貨滿滿!不少讀者已經通過閱讀「 冰河技術 」微信公眾號文章,吊打面試官,成功跳槽到大廠;也有不少讀者實現了技術上的飛躍,成為公司的技術骨幹!如果你也想像他們一樣提升自己的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公眾號吧,每天更新超硬核技術乾貨,讓你對如何提升技術能力不再迷茫!

相關文章