3.1.1 配置 profile bean

Spring 为环境相关的 bean 所提供的解决方案其实与构建时的方案没有太大的差别。当然,在这个过程中需要根据环境决定该创建哪个 bean 和不创建哪个 bean。不过 Spring 并不是在构建的时候做出这样的决策,而是等到运行时再来确定。这样的结果就是同一个部署单元(可能会是 WAR 文件)能够适用于所有的环境,没有必要进行重新构建。

在 3.1 版本中,Spring 引入了 bean profile 的功能。要使用 profile,你首先要将所有不同的 bean 定义整理到一个或多个 profile 之中,在将应用部署到每个环境时,要确保对应的 profile 处于激活(active)的状态。

在 Java 配置中,可以使用 @Profile 注解指定某个 bean 属于哪一个 profile。例如,在配置类中,嵌入式数据库的 DataSource 可能会配置成如下所示:

DataSourceConfig.java
package com.myapp;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

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

我希望你能够注意的是 @Profile 注解应用在了类级别上。它会告诉 Spring 这个配置类中的 bean 只有在 dev profile 激活时才会创建。如果 dev profile 没有激活的话,那么带有 @Bean 注解的方法都会被忽略掉。

同时,你可能还需要有一个适用于生产环境的配置,如下所示:

DataSourceConfig.java
package com.myapp;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jndi.JndiObjectFactoryBean;

@Configuration
@Profile("prod")
public class DataSourceConfig {

  @Bean
  public DataSource dataSource() {
    JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
    jndiObjectFactoryBean.setJndiName("jdbc/myDS");
    jndiObjectFactoryBean.setResourceRef(true);
    jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
    return (DataSource) jndiObjectFactoryBean.getObject();
  }

}

在本例中,只有 prod profile 激活的时候,才会创建对应的 bean。

在 Spring 3.1 中,只能在类级别上使用 @Profile 注解。不过,从 Spring 3.2 开始,你也可以在方法级别上使用 @Profile 注解,与 @Bean 注解一同使用。这样的话,就能将这两个 bean 的声明放到同一个配置类之中,@Profile 注解基于激活的 profile 实现 bean 的装配,如下所示:

DataSourceConfig.java
package com.myapp;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;

@Configuration
public class DataSourceConfig {
  
  @Bean(destroyMethod = "shutdown")
  @Profile("dev")
  public DataSource embeddedDataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.H2)
        .addScript("classpath:schema.sql")
        .addScript("classpath:test-data.sql")
        .build();
  }

  @Bean
  @Profile("prod")
  public DataSource jndiDataSource() {
    JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
    jndiObjectFactoryBean.setJndiName("jdbc/myDS");
    jndiObjectFactoryBean.setResourceRef(true);
    jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
    return (DataSource) jndiObjectFactoryBean.getObject();
  }

}

这里有个问题需要注意,尽管每个 DataSource bean 都被声明在一个 profile中,并且只有当规定的 profile 激活时,相应的 bean 才会被创建,但是可能会有其他的 bean 并没有声明在一个给定的 profile 范围内。没有指定 profile 的 bean 始终都会被创建,与激活哪个 profile 没有 关系。

在 XML 中配置 profile

我们也可以通过元素的 profile 属性,在 XML 中配置 profile bean。例如,为了在 XML 中定义适用于开发阶段的嵌入式数据库 DataSource bean,我们可以创建如下所示的 XML 文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="
    http://www.springframework.org/schema/jdbc
    http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <jdbc:embedded-database id="dataSource" >
    <jdbc:script location="classpath:schema.sql" />
    <jdbc:script location="classpath:test-data.sql" />
  </jdbc:embedded-database>
  
</beans>

与之类似,我们也可以将 profile 设置为 prod,创建适用于生产环境的从 JNDI 获取的 DataSource bean。同样,可以创建基于连接池定义的 DataSource bean,将其放在另外一个 XML 文件中,并标注为 qaprofile。所有的配置文件都会放到部署单元之中(如 WAR 文 件),但是只有 profile 属性与当前激活 profile 相匹配的配置文件才会被用到。

你还可以在根 <beans> 元素中嵌套定义 <beans> 元素,而不是为每个环境都创建一个 profile XML 文件。这能够将所有的 profile bean 定义放到同一个 XML 文件中,重复使用元素来指定多个 profile,如下所示:

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

  <beans profile="dev">
    <jdbc:embedded-database id="dataSource" type="H2">
      <jdbc:script location="classpath:schema.sql" />
      <jdbc:script location="classpath:test-data.sql" />
    </jdbc:embedded-database>
  </beans>
  
  <beans profile="prod">
    <jee:jndi-lookup id="dataSource"
      lazy-init="true"
      jndi-name="jdbc/myDatabase"
      resource-ref="true"
      proxy-interface="javax.sql.DataSource" />
  </beans>
</beans>

除了所有的 bean 定义到了同一个 XML 文件之中,这种配置方式与定义在单独的 XML 文件中的实际效果是一样的。这里有三个 bean,类型都是 javax.sql.DataSource,并且 ID 都是 dataSource。但是在运行时,只会创建一个 bean,这取决于处于激活状态的是哪个 profile。

那么问题来了:我们该怎样激活某个 profile 呢?

Last updated