Spring高级装配之环境与profile

上一节我们大致介绍了Spring中三种bean装配方案,本节我们将在它的基础上介绍一些更高级的Bean装配功能。

环境与profile

企业开发过程中常常会将一些应用从一个服务器迁移到另一个服务器。开发阶段,某些环境相关做法可能并不适合迁移到生产环境中,甚至即便迁移过去也无法正常工作。如:数据库配置、加密算法以及与外部系统的集成是跨环境部署时会发生变化。
不如我们想配置一个数据库,我们可能使用EmbeddedDatabaseBuilder;

@Bean(destroyMethod="shutdown")
public DataSource dataSource(){
    return new EmbeddedDatabaseBuilder(){
        .addScript("classpath:schema.sql")
        .addScript("classpath:test-data.sql")
        .builder();
    }
}

使用EmbarrassedDatabasedBuilder会搭建一个嵌入式的Hypersonic数据库,他的模式(schema)定义在schema.sql中,测试数据则是通过test-data.sql加载的。
这样的数据库用于测试还行,但对于生产环境,他就不太适用啦。再生产环境中你可能希望使用JNDI从容器中获取一个DataSource:

@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();
}

通过JNDI获取DataSource能够让容器决定该如何创建这个DataSource,甚至包含切换为容器管理的连接池。虽然JNDI管理的DataSource更适合生产环境,但对于简单集成和开发测试环境来说,这会带来不必要的复杂性。
同时,在QA环境中,你可以选择完全不同的DataSource配置,你可以配置为Commons DBCP连接池:

@Bean(destroyMethod="close")
public DataSource dataSource(){
    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");
    dataSource.setDriverClassName("org.h2.Driver");
    dataSource.setUsername("sa");
    dataSource.setPassword("password");
    dataSource.setInitialSize(20);
    dataSource.setMaxAction(30);

    return dataSource;
}

  • 配置Profile bean

在3.1版本中,Spring引入了bean profile的功能,要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。
在Java配置中,可以使用@Profile注解指定某个bean属于哪一个profile:

package com.cache.profile;
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;
/**
 * <dl>
 * <dd>Description:功能描述</dd>
 * <dd>Company: 黑科技</dd>
 * <dd>@date:2016年8月16日 下午9:52:32</dd>
 * <dd>@author:Kong</dd>
 * </dl>
 */
@Configuration
@Profile("dev")
public class DevlopmentProfileConfig {
    
    @Bean(destroyMethod="shutdown")
    public DataSource dataSource(){
        EmbeddedDatabaseBuilder embeddedDatabaseBuilder =new EmbeddedDatabaseBuilder();{
            return embeddedDatabaseBuilder.setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schem.sql")
            .addScript("classpath:test-data.sql")
            .build();
        }
    }
}

这时候@Profile注解应用在了类级别上。他会告诉Spring只有在dev profile激活时才会创建该Bean。如果没有激活dev profile配置,则Spring会忽略掉这个Bean。
其实在生产环境中,下面的配置更合适:

package com.cache.profile;
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.jndi.JndiObjectFactoryBean;
/**
 * <dl>
 * <dd>Description:功能描述</dd>
 * <dd>Company: 黑科技</dd>
 * <dd>@date:2016年8月16日 下午10:09:56</dd>
 * <dd>@author:Kong</dd>
 * </dl>
 */
@Configuration
@Profile("prod")
public class ProductionProfileConfig {

    @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();
    }
}

同样,本例中的Bean也是只有在profile文件被激活的时候才会被创建。
在Spring3.1时,只能在类级别上使用@Profile注解,不过从Spring3.2开始,@Profile注解就可以使用在方法级别上了。这样就能将两个bean的声明放到同一个配置类中:

package com.cache.profile;
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;
/**
 * <dl>
 * <dd>Description:功能描述</dd>
 * <dd>Company: 黑科技</dd>
 * <dd>@date:2016年8月16日 下午10:16:48</dd>
 * <dd>@author:Kong</dd>
 * </dl>
 */
@Configuration
public class DataSourceConfig {
    @Bean(destroyMethod="shutdown")
    @Profile("dev")
    public DataSource embeddedDataSource(){
        EmbeddedDatabaseBuilder embeddedDatabaseBuilder =new EmbeddedDatabaseBuilder();{
            return embeddedDatabaseBuilder.setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schem.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();
    }
}

上面这些Bean只有当对应的profile被激活才能被加载,但如果bean上没有@Profile注解,这样的bean始终是会被创建的。

XML中配置profile

我们也可以通过<beans>元素的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:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:cache="http://www.springframework.org/schema/cache"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/cache
        http://www.springframework.org/schema/cache/spring-cache.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd" 
    profile="dev">
    
    <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文件中,并标记为qa profile。所有的配置文件都会放到部署单元之中(如WAR文件),但是只有profile属性与当前激活profile配置文件相匹配的才会才会被用到。
我们还可以在根<beans>元素中嵌套定义<beans>元素,而不是为每个环境都创建一个profile XML文件。这能够将所有的profile 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:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:cache="http://www.springframework.org/schema/cache"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/cache
        http://www.springframework.org/schema/cache/spring-cache.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd">

    <beans profile = "dev">
        <jdbc:embedded-database id="dataSource">
         <jdbc:script localtion="classpath:schem.sql"/>
         <jdbc:script localtion="classpath:test-data.sql"/>
        </jdbc:embedded-database>
    </beans>
    
    <beans profile="qa">
        <beans id="dataSource" class="org.apache.dbcp.commons.dbcp.BasicDataSource"
        destory-mothos="close"
        p:url="jdbc:h2:tcp://dbserver/~/test"
        p:driverClassName="org.h2.Driver"
        p:username="sa"
        p:password="password"
        p:initialSize="20"
        p:maxActive="30"/>
        
    </beans>
    
    <beans profile="prod"
        <jee:jndi-lookup id="dataSource"
                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?

激活profile

Spring在确定激活那个profile时,需要依赖两个独立的属性:Spring.profiles.active和spring.profiles.default。如果设置了spring.profiles.active属性的话,那么Spring就会根据它去选择激活那个profile,如果spring.profiles.active没有设置值,那么Spring会根据spring.profiles.default的值选择激活哪个profille,如果两个值都没有设置,那么Spring只会创建哪些没有没有定义profile中的bean。
设置这两个属性的方式有以下几种:

  • 作为DispatcherServlet的初始化参数;
  • 作为Web应用的上下文参数;
  • 作为JNDI条目;
  • 作为环境变量;
  • 作为JVM的系统属性;
  • 在集成测试类上,使用@ActiveProfiles注解设置。
    通常我会通过设置DispatcherServlet的参数的方式来指定spring.profiles.active和spring.profiles.default,下面是在web.xml中指定他们的值:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    <display-name>testCache</display-name>

    <context-param>
        <!-- 为上下文设置默认profile -->
        <param-name>spring.profile.default</param-name>
        <param-value>dev</param-value>
    </context-param>
    
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    
    <servlet>
        <servlet-name>springServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-mvc.xml</param-value>
            <!-- 为Servlet设置默认的profile -->
            <param-name>spring.profiles.default</param-name>
            <param-value>dev</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
</web-app>

使用profile进行测试

当运行集成测试时,通常希望采用与生产环境相同的配置进行测试。但如果配置中的bean定义了profile中,那么在运行测试时,我们就需要有一种方式来启动合适的profile。
Spring就提供了@ActiveProfiles注解,来指定激活哪个profile:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfigration(classes={PersistenTestConfig})
@ActiveProfiles
public class PersistenTest(){
    ...
}

在条件化创建bean方面,Spring的profile机制是一种很好的方法,这里的条件要基于那个profile处于激活状态来判断。下节我们会介绍Spring 4.0提供的一种更为通用的机制,来实现条件化的bean定义。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容