profile的用法
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<!-- 定义开发的profile -->
<beans profile="dev">
...
</beans>
<!-- 定义生产使用的profile -->
<beans profile="product">
...
</beans>
</beans>
profile的作用
通过设置profile属性在applicationContext*.xml配置文件部署不同的环境。这里不同的环境可以是指开发-测试-生产环境,也可以是oracle-mysql-sqlserver数据库环境,也可以是jndi-c3p0-jdbc数据源环境。
profile的配置属性
在AbstractEnvironment中有2个属性:
spring.profiles.default:缺省值,如果不设置active,则为此值。
spring.profiles.active:如果设置了该值,则以该值为准,缺省值失效。
/**
* Name of property to set to specify active profiles: {@value}. Value may be comma
* delimited.
* <p>Note that certain shell environments such as Bash disallow the use of the period
* character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
* is in use, this property may be specified as an environment variable as
* {@code SPRING_PROFILES_ACTIVE}.
* @see ConfigurableEnvironment#setActiveProfiles
*/
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
/**
* Name of property to set to specify profiles active by default: {@value}. Value may
* be comma delimited.
* <p>Note that certain shell environments such as Bash disallow the use of the period
* character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
* is in use, this property may be specified as an environment variable as
* {@code SPRING_PROFILES_DEFAULT}.
* @see ConfigurableEnvironment#setDefaultProfiles
*/
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
设置profile属性的方法
我以配置jndi数据源和c3p0数据源为例,列出公共部分的代码。目录如下:
applicationContext.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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 定义开发的profile -->
<beans profile="dev">
<!-- 只扫描开发环境下使用的类 -->
<context:component-scan base-package="com.bcu.service.jndi" />
<!-- 加载开发使用的配置文件 通过@Value("#{config.xxx}")来注入属性 -->
<util:properties id="config" location="classpath:dev/config.properties"/>
<context:property-placeholder location="classpath*:dev/*.properties"/>
</beans>
<!-- 定义生产使用的profile -->
<beans profile="product">
<!-- 只扫描生产环境下使用的类 -->
<context:component-scan
base-package="com.bcu.service.c3p0" />
<!-- 加载生产使用的配置文件 通过@Value("#{config.xxx}")来注入属性 -->
<util:properties id="config" location="classpath:product/config.properties"/>
<context:property-placeholder location="classpath*:product/*.properties"/>
</beans>
</beans>
获取数据源的公共接口:
package com.bcu.service;
import javax.sql.DataSource;
import org.springframework.stereotype.Service;
@Service
public interface DatasourceService {
public DataSource getDataSource();
}
jndi数据源类:
package com.bcu.service.jndi;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.bcu.service.DatasourceService;
@Component
public class JndiSource implements DatasourceService {
//在开发环境下从配置文件中注入,config.name为util:properties标签解析的配置文件下的值
//util:properties的作用:从配置文件中动态注入属性
@Value("#{config.name}")
private String name;
@Override
public DataSource getDataSource() {
System.out.println(name+"数据源,获取成功!");
return null;
}
}
c3p0数据源类:
package com.bcu.service.c3p0;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.bcu.service.DatasourceService;
@Component
public class C3p0Source implements DatasourceService {
//在开发环境下从配置文件中注入,config.name为util:properties标签解析的配置文件下的值
//util:properties的作用:从配置文件中动态注入属性
@Value("#{config.name}")
private String name;
@Override
public DataSource getDataSource() {
System.out.println(name+"数据源,获取成功!");
return null;
}
}
dev/config.properties:
product/config.properties
①在web.xml中配置作为web应用上下文的参数
web.xml:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- 通过context-param设置spring.profile.default -->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</context-param>
<!-- 通过context-param设置spring.profile.active -->
<!-- 设置active属性后,default属性失效 -->
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>product</param-value>
</context-param>
<servlet>
<servlet-name>datasource</servlet-name>
<servlet-class>com.bcu.servlet.DataSourceServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- web启动读取applicationContext.xml -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
</web-app>
DataSourceServlet:
package com.bcu.servlet;
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import com.bcu.service.DatasourceService;
public class DataSourceServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
@Autowired
private DatasourceService service;
@Override
public void init(ServletConfig config) throws ServletException {
/* 在servlet中使用@Autowired注入service,会报空指针,需要加入下面这段代码
* 在servlet容器中注入spring容器管理的bean
*/
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, config.getServletContext());
service.getDataSource();
}
//其他方法省略。。。
}
启动效果:
如果将web.xml中的dev改成product:
②作为环境变量
在环境变量-系统变量中加spring.profiles.active,需要重启才能生效。
③作为jvm参数
加入以下启动参数:
-Dspring.profiles.active="dev"
④前面都是依赖于启动服务器来完成配置,而这种方法是在继承测试类上完成的,通过@ActiveProfiles("dev")来激活profile属性
package com.bcu.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.bcu.entity.User;
import com.bcu.service.DatasourceService;
@RunWith(SpringJUnit4ClassRunner.class)
//加载applicationContext.xml
@ContextConfiguration(locations="classpath:applicationContext.xml")
//通过注解的方式完成对profile的激活
@ActiveProfiles("dev")
public class UnitTets {
@Autowired
private DatasourceService service;
@Test
public void test1() throws Exception{
service.getDataSource();
}
}
请大家思考一个问题:spring如何通过profile属性来确定要注册哪些bean?
在spring-bean源码的DefaultBeanDefinitionDocumentReader中,doRegisterBeanDefinitions方法对配置文件先进性过滤性处理,再进行解析、注册bean。
请看以下代码:
/**
* Register each bean definition within the given root {@code <beans/>} element.
*/
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
//判断是否是默认的命名空间
if (this.delegate.isDefaultNamespace(root)) {
//获取到documnet对象中profile的节点值
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
//判断是否有值
if (StringUtils.hasText(profileSpec)) {
//以“,;”分割拆分成数组
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
//获取上下文环境,判断该profile的值是否被激活,如果激活,则继续,否则不读取该节点的配置
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
//解析并注册bean
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
首先判断是否是默认的命名空间,其次获取到documnet对象中profile的节点值,也就是<bean profile="">里的值,然后以“,;”分割拆分成数组,然后获取上下文环境,判断该profile的值是否被激活,如果激活,则继续,否则不读取该节点的配置。