上一节我们大致介绍了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定义。