Elim的博客

用来记录一些原创性的总结


Elim

19 Profile

有的时候我们可能需要在不同的环境下使用不同的bean定义,如在开发环境直接使用直接定义的数据源,而在生产环境使用对应的JNDI数据源等。针对这种需求,Spring给我们引入了一个profile的概念,其允许我们将在特定环境下需要使用的bean定义为不同的profile,然后只有在对应的profile激活的情况下才使用对应的bean定义。打个比方我们有一个beanA需要在开发环境才启用,则可以定义其对应的profile为dev,然后另外有一个beanB需要在生产环境才启用,则可以定义其对应的profile为production。那么只有当我们指定对应的profile为dev时beanA才会被激活,只有profile为production时beanB才会被启用,其它情况下都是未启用的。

19.1 指定profile

针对不同的bean定义方式,对应的profile的指定方式也是不一样的。

19.1.1 基于XML配置定义的bean

对于这种形式的bean定义,对应的profile指定是通过在<beans/>标签上的profile属性进行指定的。如下示例我们就通过在<beans/>标签上指定了profile为dev,那么对应<beans/>中定义的所有的bean都只有在profile为dev时才可用,这也包括其中通过<context:component-scan/>定义扫描到的其它bean定义。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd"
    profile="dev">

	<bean id="hello" class="com.elim.learn.spring.bean.Hello"/>

</beans>

profile也可以定义在内置的标签上,如下我们指定了当前文件的profile为dev,但是在其内部定义了一个内置的<beans/>标签,并指定其对应的profile为production,这样只有在dev和production两种profile都激活时,Spring才会扫描对应的类路径进行bean定义,因为我们在最顶层的<beans/>上指定了profile为dev,在<context:component-scan/>上级<beans/>上指定了profile为production,对于这种嵌套指定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:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd"
    profile="dev">
	<!-- 只有profile为production时才进行扫描 -->
	<beans profile="production">
		<context:component-scan base-package="com.app">
			<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
		</context:component-scan>
	</beans>
	<!-- 只有profile为dev时才可用 -->
	<beans>
		<bean id="hello" class="com.elim.learn.spring.bean.Hello"/>
	</beans>

</beans>

在上述示例中如果我们希望<context:component-scan/>能够在production激活的情况就生效,而不用管dev,则可以取消最顶层的profile=”dev”。
此外,profile除了可以直接指定一个值以外,还可以同时指定多个profile,中间以逗号隔开,表示只要其中一个profile是激活状态即可启用当前的定义。如果profile是以感叹号“!”开始的,则表示需要对应的profile没有激活的情况下才可用。

	<!-- 当p1或p2对应的profile为激活状态时,当前定义才是可用的 -->
	<beans profile="p1,p2">
		<!-- .... -->
	</beans>
	
	<!-- 当没有激活p1对应的profile时,当前定义才是可用的 -->
	<beans profile="!p1">
		<!-- .... -->
	</beans>

19.1.2 自动扫描的bean定义

对于自动扫描的bean定义,如果我们是需要将所有的扫描类统一使用一种profile,则对于基于XML配置的bean容器定义我们可以使用<beans/>标签包裹<context:component-scan/>,并在<beans/>标签上通过profile属性指定对应的profile。而对于基于Java类配置的自动扫描,如果需要将所有的扫描类统一使用一种profile,则可以在对应的配置类上使用@Profile进行标注,并通过其value属性指定对应的profile。
如果我们只是希望将自动扫描的某些类指定为特定的profile,则可以在对应的类上使用@Profile进行标注,并通过对应的value属性指定对应的profile。如下示例就指定了当前bean对应的profile为dev。

@Component
@Profile("dev")
public class Hello {
	
}

使用@Profile时也可以同时指定多个profile,这个时候多个profile之间的关系就是或,即只要其中的某个profile处于激活状态当前定义即为可用。如下示例即表示当p1或p2对应的profile处于激活状态时,如下定义才是可用的。

@Component
@Profile({"p1", "p2"})
public class Hello {

}

使用@Profile时也可以使用“!”前缀,表示只有在对应的profile不处于激活状态时当前定义才是可用的。如下示例即表示只有在p1对应的profile不处于激活状态时对应的定义才是可用的。

@Component
@Profile({"!p1"})
public class Hello {

}

此外,我们还可以自定义一个注解,然后使用@Profile进行标注,并指定对应的profile,这样我们就可以使用该自定义注解来替代特定的@Profile来使用。如在我们的应用中有许多bean需要使用@Profile(“production”)进行标注,那么我们就可以自定义如下这样一个@Production注解来代替@Profile(“production”)。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {

}

如果原来我们有一个bean定义成如下这样。

@Component
@Profile("production")
public class Hello {
	
}

因为我们的自定义的@Production注解上使用了@Profile进行标注,并且指定了profile为production,那么可以用@Production来替代@Profile(“production”),所以上述定义也可以定义成如下这样。

@Component
@Production
public class Hello {
	
}

19.1.3 基于Java类配置定义的bean

对于基于Java类的配置我们可以在对应的配置类上使用@Profile来指定整个配置类对应的bean定义都必须在特定的profile下才可用,如下示例,我们指定了配置类SpringConfig只有在profile为dev的情况下才是可用的,这包括直接在SpringConfig中定义的bean,也包括通过@Import引入的其它配置类中定义的bean,还包括通过@ImportResource引入的XML文件中定义的bean,都只能在profile为dev时才可用。

@Configuration
@Profile("dev")
public class SpringConfig {

	@Bean
	public Hello hello() {
		return new Hello();
	}
	
	@Bean
	public World world() {
		return new World();
	}
	
}

当我们只需要指定某个bean对应的profile时,我们可以在对应的bean定义上使用@Profile进行定义。当Java配置类和实际的bean定义方法上都使用@Profile指定了profile时表示两者都需要满足才行。如下示例,表示hello是在profile为dev的情况下可用,它自己没有指定,而是从Java配置类SpringConfig继承来的。而world将需要dev和production两种profile都激活的情况下才是可用的。

@Configuration
@Profile("dev")
public class SpringConfig {

	@Bean
	public Hello hello() {
		return new Hello();
	}
	
	@Bean
	@Profile("production")
	public World world() {
		return new World();
	}
	
}

当Java配置类上没有指定@Profile,而直接在bean定义上指定了@Profile时则表示当前的bean需要在指定的profile激活的情况下才可用。如下示例中hello将在任何profile下都是可用的,而world将只有在激活了production这种profile的情况下才是可用的。

@Configuration
public class SpringConfig {

	@Bean
	public Hello hello() {
		return new Hello();
	}
	
	@Bean
	@Profile("production")
	public World world() {
		return new World();
	}
	
}

19.2 指定启用的profile

前面已经介绍了profile的指定,我们知道指定了profile后则表示对应的内容只有在特定的profile下才会生效。当前应用究竟使用的是哪个profile,或者是哪些profile,这是需要我们来指定的。说的专业一点就叫激活,即只有处于激活状态的profile对应的定义才会生效,当然也包括那些没有指定profile的定义。

在Spring中激活哪个profile是通过参数spring.profiles.active来指定的,我们可以把它定义为一个系统环境变量、JVM参数,或者是在web.xml中的一个ServletContext参数。如下就是通过JVM参数指定激活的profile为dev的示例。

-Dspring.profiles.active=dev

如下是通过在web.xml文件中通过ServletContext的参数指定激活的profile的示例,其激活的profile是dev。

<context-param>
	<param-name>spring.profiles.active</param-name>
	<param-value>dev </param-value>
</context-param>

当然,我们也可以同时激活多个profile,同时激活多个profile时,多个profile之间以逗号隔开。如下示例即表示同时激活dev和production两个profile。(其它如JVM参数指定等是同样的规则)

<context-param>
	<param-name>spring.profiles.active</param-name>
	<param-value>dev,production</param-value>
</context-param>

除了使用spring.profiles.active参数进行指定外,我们还可以通过在程序中动态的指定激活的profile。如下示例中我们就通过获取当前ApplicationContext的Environment对象,然后通过该对象指定激活的profile为production。使用程序指定激活的profile时需要注意先构建一个空的ApplicationContext对象,然后再通过该对象的Environment对象指定激活的profile,再指定对应的bean定义对应的资源位置,最后通过调用refresh()方法让ApplicationContext对象解析对应的bean定义。

	ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
	context.getEnvironment().setActiveProfiles("production");
	context.setConfigLocation("classpath:applicationContext.xml");
	context.refresh();

如果需要同时指定多个激活的profile,则可以给setActiveProfiles()方法指定多个参数,其对应的参数是一个可变参数。

	context.getEnvironment().setActiveProfiles("dev","production");

19.3 默认profile

此外,我们还可以给我们的应用指定默认的profile。我们知道如果一个<beans/>没有指定profile,且其上级的<beans/>也没有指定profile,那么对应<beans/>中定义的所有的bean无论激活的何种profile,它们都是可用的。而默认profile的概念是我们定义一个默认的profile,然后如果一个<beans/>指定的profile为默认的profile,则当没有激活的profile时,对应默认profile的<beans/>中定义的bean都是可用的,但是一旦有激活的profile,那么对应默认profile的<beans/>就是不可用的。如果我们默认的bean定义不指定profile的话,那么对应的bean定义将在所有的情况下都是可用的,一旦我们改变profile,那么可能就会存在两个相同类型的bean定义。又或者我们将默认的bean定义与特定的bean定义定义为不同的两个profile,这样的结果是我们必须指定一个激活的profile。所以说默认profile这种机制也是非常有用的,即我们可以通过默认profile来定义默认的bean定义,然后通过改变profile来改变对应的bean定义。

Spring中默认profile的名称是“default”,即默认情况下我们将一个<beans/>的profile指定为default,即表示其对应默认的profile。如下示例中我们定义了在没有处于激活状态的profile时hello_default是可用的,而在名称为production的profile处于激活状态时hello_production是可用的。

<!-- 只有在激活了名称为production的profile时其中定义的bean才是可用的 -->
<beans profile="production">
	<bean id="hello_production" class="com.app.Hello"/>
</beans>

<!-- 默认profile,即只有在没有激活任何profile的情况下其中定义的bean才是可用的 -->
<beans profile="default">
	<bean id="hello_default" class="com.app.Hello"/>
</beans>

19.3.1 更改默认profile的名称

默认profile的名称是“default”,我们也可以通过spring.profiles.default参数进行更改,更改方式类似于通过参数spring.profiles.active指定激活的profile。

1、如下是通过JVM参数指定默认的profile为production。

	-Dspring.profiles.default=production

2、如下是通过ServletContext的参数指定默认的profile为production(供ContextLoaderListener使用)。

<context-param>
	<param-name>spring.profiles.default</param-name>
	<param-value>production</param-value>
</context-param>

对于这种直接通过参数spring.profiles.default指定默认profile的情况,我们也可以同时指定多个profile,多个profile之间以逗号隔开。

3、也可以通过程序化的方式获取ApplicationContext对应的Environment对象,然后通过该对象设置对应的默认profile。如下示例表示我们设置默认的profile为“default”和“production”。setDefaultProfiles()方法接收的是一个可变参数,所以我们可以同时指定多个默认的profile。

	ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
	context.getEnvironment().setDefaultProfiles("production", "default");
	context.setConfigLocation("classpath:applicationContext.xml");
	context.refresh();

(注:本文是基于Spring4.1.0所写)