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定義。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容