spring中profile属性实现开发、测试、生产环境的切换

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的值是否被激活,如果激活,则继续,否则不读取该节点的配置。

猜你喜欢

转载自blog.csdn.net/ZixiangLi/article/details/87877018
今日推荐