上一節(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定義。