多環境支援-Maven和Spring的Profile

xiaosunzhu發表於2017-08-04

摘要

多環境支援是每個專案都要面對的問題,且不說很多環境,哪怕簡單的專案也是需要區分本地開發的環境和正式執行的生產環境,不同的專案架構有不同的實現方式,這篇文章先說明利用Profile來進行配置的方式。
如今使用Spring Boot的專案已經很多了,用起來也算方便,下面來看看使用Maven作為構建工具,Spring Boot為基礎框架,如何在不同的環境實現不同的執行狀態。

Maven的profile

眾所周知,Maven是支援profile配置的,在pom.xml配置檔案中可以配置,在執行mvn命令時,可以指定profile:

mvn -P [profile]

很容易想到利用profile配置引數,然後根據引數打包不同的資源,這樣就可以實現指定不同的profile時可以用不同的資源打包,就可以以不同的狀態執行,實現目的。
下面將舉一個簡單的例子,實現打包不同的配置資源目錄。
pom.xml

    ……
    <profiles>
        <profile>
            <id>local</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <env>local</env>
            </properties>
        </profile>

        <profile>
            <id>real</id>
            <properties>
                <env>release</env>
            </properties>
        </profile>
    </profiles>

    <build>
        <resources>
            ……
            <resource>
                <directory>src/main/resources-${env}</directory>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
            ……
        </resources>
        ……
    </build>
    ……

這段pom配置定義了兩個profile:local和real,預設使用的是local,也就是說在不指定profile的時候將使用local。兩個profile都定義了<env>的屬性,屬性值是不同的,這個屬性可以在pom的其他地方引用,這個例子就在指定資源目錄時引用了env屬性值,因此,在指定profile為local時,將資源目錄將使用resources-local目錄,而profile指定為real時,將使用resources-release目錄。除此了可以使用不同的資源目錄,還可以實現很多目的,比如:使用不同的配置檔案、為外掛配置不同的引數等等。
下面再舉一個常見例子,絕大部分專案都會用到資料庫,可以使用flywaydb工具進行版本管理,但是一般生產環境不應當使用這種自動化管理工具,因此,僅僅在本地開發(或者測試)環境使用:
pom.xml

    <build>
        ……
        <plugins>
            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <version>4.2.0</version>
                <configuration>
                    <url>${flyway.url}</url>
                    <user>${flyway.user}</user>
                    <password>${flyway.password}</password>
                </configuration>
            </plugin>
        </plugins>
        ……
    </build>

    <profiles>
        <profile>
            <id>local</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <flyway.url>jdbc:mysql://localhost:3306/testdb?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false</flyway.url>
                <flyway.user>admin</flyway.user>
                <flyway.password>admin</flyway.password>
            </properties>
        </profile>
        <profile>
            <id>test</id>
            <properties>
                <flyway.url>jdbc:h2:nio:${project.build.directory}/db/test</flyway.url>
                <flyway.user>ncp_console</flyway.user>
                <flyway.password>console123!@#</flyway.password>
            </properties>
        </profile>
        <profile>
            <id>release</id>
            <properties>
                <flyway.url></flyway.url>
                <flyway.user></flyway.user>
                <flyway.password></flyway.password>
            </properties>
        </profile>
    </profiles>

這段pom配置就可以達到目的,本地開發環境使用flyway管理本地mysql資料庫,測試環境使用flyway管理h2資料庫,生產環境flywaydb沒有任何作用。

Spring的profile

spring和maven的profile

spring與maven一樣都稱為profile,很顯然是類似的作用,但是要注意,二者並沒有關聯。spring的profile真正強大在於可以與spring結合,在執行時其實可以達到更多更靈活的目的,比如上下文不去載入一些bean等等,後面能看到一些示例。
既然已經有了maven的profile可以支援多環境配置,為什麼還要介紹Spring的profile呢?在我看來,至少有一個理由可以決定是否要使用Spring的profile:是否僅提供一個釋出包。也就是說,無論是在開發環境或者測試環境或者生產環境,都是提供相同的包,對於絕大多數情況,僅提供一個釋出包是適用的,也是便於管理的。如果僅提供一個釋出包,也就意味著在maven構建階段,是不能區分profile的,因此就要靠spring了。

指定profile

Spring boot為profile的配置提供了很好的支援,比如預設的application配置檔案預設就支援按照profile使用對應的application-{profile}配置檔案,非常方便,具體參考官方手冊。下面介紹幾種常用的指定spring的當前profile(ActiveProfile):
- JVM引數指定profile:java …… -Dspring.profiles.active=local
- 註解指定profile: @ActiveProfiles("test"),這個註解是針對型別的註解,並且具有@Inherited特性,就是如果父類配置了,子類會繼承該註解。
- 配置檔案中指定profile:在application配置檔案中可以配置spring.profiles.active=dev來指定profile。
完整的方法可以閱讀Spring官方手冊(包括spring.profiles.default和spring.profiles.active兩個屬性)、設定profile,還可以在程式碼中指定、web配置中指定等等。

profile的應用

僅僅指定profile是沒有任何意義的,下面介紹一些常見用法:

定義Bean的時侯設定profile

如果是傳統xml配置bean,可以配置<beans profile="local" ……>;如果使用註解方式,就在元件上新增註解@Profile("local")@Profile還可以作為元註解去配置自定義註解)。比如官方的示例:

    @Configuration
    @Profile("development")
    public class StandaloneDataConfig {
        @Bean
        public DataSource dataSource() {
            return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .addScript("classpath:com/bank/config/sql/test-data.sql")
                .build();
        }
    }

    @Configuration
    @Profile("production")
    public class JndiDataConfig {
        @Bean(destroyMethod="")
        public DataSource dataSource() throws Exception {
            Context ctx = new InitialContext();
            return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
        }
    }

那麼在指定profile為development時,”dataSource”的bean就是StandaloneDataConfig中定義的bean,使用嵌入式資料庫;而如果指定profile為production時,”dataSource”的bean就是JndiDataConfig中定義的bean,使用Jndi中註冊的資料來源。

profile區分預設配置檔案(以properties檔案為例)

spring boot預設會使用application.properties配置檔案,如果指定了profile,比如”local”,那麼spring會按約定載入application-local.properties配置檔案,並用application-local.properties中的配置覆蓋application.properties中的配置。比如:

application.properties
    server.port=8082
    logging.config=classpath:log4j2.xml

application-local.properties
    logging.config=classpath:log4j2-local.xml

按照上面的配置,在沒有指定profile時,log4j2的配置檔案將使用log4j2.xml,如果指定profile為local時,將使用log4j2-local.xml,這樣就可以在本地開發環境中將日誌配置為trace級別,而其他環境配置為較高階別。但是因為application-local.properties中沒有配置server.port,因此,無論是否指定profile為local,服務埠都是8082。

在配置註解中引用profile值

我們知道spring的配置可以使用${…}引用,當前指定的profile也可以通過${spring.profiles.active}引用。比如如果想自定義配置檔案,而又要支援不同環境使用不同配置,可以這樣:

project-local.properties
    recent.size=3

project-real.properties
    recent.size=10
    @SpringBootApplication
    @PropertySource("classpath:project-${spring.profiles.active}.properties")
    public class MainLauncher {
        ……
    }

    public class RecentRepository {

        @Value("${recent.size}")
        public int recentSize;
        ……
    }

在自定義配置檔案時,通過引入profile值在不同環境下載入不同的自定義配置檔案,在local環境,recentSize=3,在real環境,recentSize=10
以上這些用法都是比較常見的,已經能夠實現絕大部分的需求了。

大家注意profile用於區分配置檔案,下一篇介紹根據條件載入bean中還可以看到這個用法,結合載入條件的定義,還可以實現更強大的功能。

相關文章