Spring高級裝配之環(huán)境與profile

上一節(jié)我們大致介紹了Spring中三種bean裝配方案,本節(jié)我們將在它的基礎(chǔ)上介紹一些更高級的Bean裝配功能。

環(huán)境與profile

企業(yè)開發(fā)過程中常常會將一些應(yīng)用從一個服務(wù)器遷移到另一個服務(wù)器。開發(fā)階段,某些環(huán)境相關(guān)做法可能并不適合遷移到生產(chǎn)環(huán)境中,甚至即便遷移過去也無法正常工作。如:數(shù)據(jù)庫配置、加密算法以及與外部系統(tǒng)的集成是跨環(huán)境部署時會發(fā)生變化。
不如我們想配置一個數(shù)據(jù)庫,我們可能使用EmbeddedDatabaseBuilder;

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

使用EmbarrassedDatabasedBuilder會搭建一個嵌入式的Hypersonic數(shù)據(jù)庫,他的模式(schema)定義在schema.sql中,測試數(shù)據(jù)則是通過test-data.sql加載的。
這樣的數(shù)據(jù)庫用于測試還行,但對于生產(chǎn)環(huán)境,他就不太適用啦。再生產(chǎn)環(huán)境中你可能希望使用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能夠讓容器決定該如何創(chuàng)建這個DataSource,甚至包含切換為容器管理的連接池。雖然JNDI管理的DataSource更適合生產(chǎn)環(huán)境,但對于簡單集成和開發(fā)測試環(huán)境來說,這會帶來不必要的復(fù)雜性。
同時,在QA環(huán)境中,你可以選擇完全不同的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中,在將應(yīng)用部署到每個環(huán)境時,要確保對應(yīng)的profile處于激活(active)的狀態(tài)。
在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注解應(yīng)用在了類級別上。他會告訴Spring只有在dev profile激活時才會創(chuàng)建該Bean。如果沒有激活dev profile配置,則Spring會忽略掉這個Bean。
其實在生產(chǎn)環(huán)境中,下面的配置更合適:

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文件被激活的時候才會被創(chuàng)建。
在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只有當(dāng)對應(yīng)的profile被激活才能被加載,但如果bean上沒有@Profile注解,這樣的bean始終是會被創(chuàng)建的。

XML中配置profile

我們也可以通過<beans>元素的profile屬性,在XML中配置profile bean。例如為了在XML中定義使用于開發(fā)階段的嵌入式數(shù)據(jù)庫DataSource bean,我們可以創(chuàng)建如下所示的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設(shè)置為prod,創(chuàng)建使用試用于生產(chǎn)環(huán)境的JNDI獲取的DataSource bean。也可以創(chuàng)建基于連接池定義的DataSource bean,將其放在另一個XML文件中,并標(biāo)記為qa profile。所有的配置文件都會放到部署單元之中(如WAR文件),但是只有profile屬性與當(dāng)前激活profile配置文件相匹配的才會才會被用到。
我們還可以在根<beans>元素中嵌套定義<beans>元素,而不是為每個環(huán)境都創(chuàng)建一個profile XML文件。這能夠?qū)⑺械膒rofile 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。但運行時,只會創(chuàng)建一個bean,這取決于處于激活狀態(tài)的是哪個profile。
下面我們就介紹一下如何激活某個profile?

激活profile

Spring在確定激活那個profile時,需要依賴兩個獨立的屬性:Spring.profiles.active和spring.profiles.default。如果設(shè)置了spring.profiles.active屬性的話,那么Spring就會根據(jù)它去選擇激活那個profile,如果spring.profiles.active沒有設(shè)置值,那么Spring會根據(jù)spring.profiles.default的值選擇激活哪個profille,如果兩個值都沒有設(shè)置,那么Spring只會創(chuàng)建哪些沒有沒有定義profile中的bean。
設(shè)置這兩個屬性的方式有以下幾種:

  • 作為DispatcherServlet的初始化參數(shù);
  • 作為Web應(yīng)用的上下文參數(shù);
  • 作為JNDI條目;
  • 作為環(huán)境變量;
  • 作為JVM的系統(tǒng)屬性;
  • 在集成測試類上,使用@ActiveProfiles注解設(shè)置。
    通常我會通過設(shè)置DispatcherServlet的參數(shù)的方式來指定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>
        <!-- 為上下文設(shè)置默認(rèn)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設(shè)置默認(rèn)的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進(jìn)行測試

當(dāng)運行集成測試時,通常希望采用與生產(chǎn)環(huán)境相同的配置進(jìn)行測試。但如果配置中的bean定義了profile中,那么在運行測試時,我們就需要有一種方式來啟動合適的profile。
Spring就提供了@ActiveProfiles注解,來指定激活哪個profile:

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

在條件化創(chuàng)建bean方面,Spring的profile機制是一種很好的方法,這里的條件要基于那個profile處于激活狀態(tài)來判斷。下節(jié)我們會介紹Spring 4.0提供的一種更為通用的機制,來實現(xiàn)條件化的bean定義。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容