Spring 3.1 新機能 - Bean Definition Profiles と Environment XML版

3.1からProfileという仕組みを使ってBeanの定義を簡単に切り替えられました。RailsとかGrailsだとかSeasar2とかにも似たのがありますね。それです。

やり方はXMLで定義する方法と、Javaのクラスに対してアノテーションを設定する方法がありますが、まずはXMLの方から見ていきます。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
                      http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
                      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

  <context:property-placeholder location="classpath*:META-INF/spring/*.properties" />

  <context:component-scan base-package="org.yamkazu.spring31" />

  <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>

  <tx:annotation-driven transaction-manager="transactionManager" />

  <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
    <property name="persistenceUnitName" value="persistenceUnit" />
    <property name="dataSource" ref="dataSource" />
  </bean>

  <beans profile="production">
    <bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource">
      <property name="driverClassName" value="${database.driverClassName}" />
      <property name="url" value="${database.url}" />
      <property name="username" value="${database.username}" />
      <property name="password" value="${database.password}" />
      <property name="testOnBorrow" value="true" />
      <property name="testOnReturn" value="true" />
      <property name="testWhileIdle" value="true" />
      <property name="timeBetweenEvictionRunsMillis" value="1800000" />
      <property name="numTestsPerEvictionRun" value="3" />
      <property name="minEvictableIdleTimeMillis" value="1800000" />
    </bean>
  </beans>

  <beans profile="dev">
    <jdbc:embedded-database id="dataSource" type="H2" />
  </beans>

</beans>

profileはbeansの属性として定義します。ここではproductionとdevという二つのProfileを定義して実行時のProfile情報を元にdataSourceが切り替わるようにしています。beansは上の様にbeansの中に入れ子にしてもいいですし(ネストで書けるようになったのは3.1からです)、従来通りファイル自体に切り離して、rootエレメントとして定義しても問題ありません。

あとは実行時にこのProfileを指定して実行するだけです。この実行時のProfileを指定するのがEnvironmentというしくみです。テストから見てみます。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/META-INF/spring/*.xml")
@ActiveProfiles({ "dev" })
public class AccountRepositoryTest {
    @Inject
    AccountRepository accountRepository;
    @Test
    public void 一件登録して取得する() {
        Account account = new Account();
        account.setName("aaa");
        accountRepository.persist(account);
        Account find = accountRepository.find(account.getId());
        assertThat(find.getName(), is(equalTo("aaa")));
    }
}

従来のSpringのJUnitでテストをする設定に加えて@ActiveProfilesというアノテーションを指定してProfile情報を定義します。分かりやすいようにわざと配列形式で指定しましたが、有効にしたいProfileは複数定義することが可能です。実はProfileの定義も複数指定できます。例えば

  <beans profile="dev1,dev2">
    <jdbc:embedded-database id="dataSource" type="H2" />
  </beans>

のような形です。

次はプログラム内でEnvironmentを指定する方法を見ていきます。

public class AccountRepositoryXmlTest {

    @Test
    public void 一件登録して取得する() {
        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
        ctx.getEnvironment().setActiveProfiles("dev");
        ctx.load("classpath:/META-INF/spring/*.xml");
        ctx.refresh();

        AccountRepository accountRepository = ctx.getBean(AccountRepository.class);

        Account account = new Account();
        account.setName("aaa");
        accountRepository.persist(account);
        Account find = accountRepository.find(account.getId());
        assertThat(find.getName(), is(equalTo("aaa")));
    }

}

contextのgetEnvironment().setActiveProfiles("xx")を呼び出して設定します。

実行時のシステムプロパティーに設定する方法もあります。

public class AccountRepositoryXmlTest {

    @Test
    public void 一件登録して取得する() {
        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
        ctx.load("classpath:/META-INF/spring/*.xml");
        ctx.refresh();

        AccountRepository accountRepository = ctx.getBean(AccountRepository.class);

        Account account = new Account();
        account.setName("aaa");
        accountRepository.persist(account);
        Account find = accountRepository.find(account.getId());
        assertThat(find.getName(), is(equalTo("aaa")));
    }

}

上記のコードを

-Dspring.profiles.active="dev"

というspring.profiles.activeに有効にしたいprofileを指定することが出来ます。

ちなみにWebアプリケーションの場合はweb.xmlからEnvironmentを指定できます。

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>spring.profiles.active</param-name>
        <param-value>production</param-value>
    </init-param>
</servlet>