設定 Profile


應用程式在開發時會設定自己的相關資源,例如開發用的資料庫,測試的環境中也會使用自己的資源,上線之後又會是另一個,雖然說,轉移至不同情境時,修改一下設定檔也可以應付,然而,如果必須修改的項目很多,也是蠻麻煩的。

為了要能使用記憶體內嵌資料庫,以及稍後可以進行單元測試,可以在 build.gradle 中加入 org.springframework:spring-jdbcorg.springframework:spring-test

apply plugin: 'java-library'

repositories {
    jcenter()
}

dependencies {
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.springframework:spring-test:5.1.2.RELEASE'

    compile 'com.h2database:h2:1.4.196'

    compile 'org.springframework:spring-context:5.1.2.RELEASE'
    compile 'org.springframework:spring-jdbc:5.1.2.RELEASE'
}

你可以使用 @Profile 來標註 Bean 的使用環境,例如,在測試時使用記憶體內嵌資料庫:

package cc.openhome;

import javax.sql.DataSource;

import org.h2.jdbcx.JdbcDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

@Configuration
@ComponentScan
@PropertySource("classpath:jdbc.properties")
public class AppConfig { 
    @Value("${cc.openhome.jdbcUrl}")
    private String jdbcUrl;

    @Value("${cc.openhome.user}")
    private String user;

    @Value("${cc.openhome.password}")
    private String password;

    @Profile("dev")
    @Bean
    public DataSource getDataSource() {
        JdbcDataSource dataSource = new JdbcDataSource();
        dataSource.setURL(jdbcUrl);
        dataSource.setUser(user);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Profile("test")
    @Bean(destroyMethod="shutdown")
    public DataSource dataSource(){
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("classpath:schema.sql")
                .addScript("classpath:testData.sql")
                .build();
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer
                       propertySourcesPlaceholderConfigurer() {
       return new PropertySourcesPlaceholderConfigurer();
    }    
}

在這邊看到兩個 Bean 都傳回 DataSource,而這兩個 Bean 分別標示為 "dev""test",可以藉由在執行 JVM 時加上 -Dspring.profiles.active=test(或者是設定環境變數 JAVA_OPTIONS="-Dspring.profiles.active=test")來選擇使用哪個 Bean。

在進行單元測試時,可以使用 @ActiveProfiles 來指定要使用的 Profile。例如:

package test.cc.openhome;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import cc.openhome.AppConfig;
import cc.openhome.model.UserService;

import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
@ActiveProfiles("test")
public class MainTest {
    @Autowired
    private UserService userService;

    @Test public void testMain() {
        userService.messages("caterpillar")
                   .forEach(message -> {
                        assertEquals("username should return 'caterpillar'", 
                                      "caterpillar", message.getUsername());
                   });
    }
}

單元測試時,可以使用 @RunWith 指定使用 Spring 實作的 Runner,@ContextConfiguration 指定設定檔來源,如此 Spring 就可以自動綁定想要的 Bean 元件,而 @ActiveProfiles 可以指定要使用的 Profile。

你可以在 SpringDI3 找到上面這個範例專案。