在介绍Spring核心模块为运行环境管理提供的功能以前,我们先得解释清楚“运行环境”是什么。java
码砖早年,对上下文(Context)、环境(Environment)一直都是傻傻分不清楚,感受2者都是放了一堆参数在里面,貌似并无多大区别。后来才慢慢摸清楚这2个词的套路。上下文(Context)是用来处理分层传递的,不清楚的能够看看上下文与IoC一文关于ApplicationContext的介绍。git
而环境(Environment)是指当前运行程序以外的各类“全局变量”,这些变量反映了当前软件运行的各类外部状况。例如咱们执行System.getenv()方法,就会获取到当前包括操做系统、全局路径配置、磁盘、jdk版本等等信息。这些信息实际上与当前运行的程序是无关的——不管你是否启动JVM,这些环境变量都是客观存在的。spring
既然环境的做用是体现当前运行的各类外部状况,那么除了JVM启动时提供的固定参数,也能够指定咱们须要的环境变量。例如咱们最多见的环境——开发环境、测试环境、集成QA环境、仿真环境、生产环境等。bash
对于软件开发而言常常要控制的就是当前程序是在开发环境运行仍是在生产环境运行。除了后面要介绍的Spring Profile功能,还有各类各样的方法来进行控制,好比Maven的profile标签。Spring Profile只是一种环境控制的参考手段,他的好处是能够在代码级别去控制,具体使用什么根据项目的须要去考量。ide
Spring的Profile特性使用起来并不复杂,并且同时支持Java注解和XML配置。咱们经过几段代码来讲明如何使用Profile。测试
(如下案例的可执行代码请到gitee下载,)ui
定义一个servuce接口和三个service的实现类:spa
package chkui.springcore.example.hybrid.profile.service; public interface Blizzard { String getName(); }
package chkui.springcore.example.hybrid.profile.service.blizzard; class Warcraft implements Blizzard { public String getName() { return "Warcraft"; } } class WorldOfWarcraft implements Blizzard { public String getName() { return "World of Warcraft"; } } class Overwatch implements Blizzard { public String getName() { return "Overwatch"; } }
而后咱们经过纯Java配置讲接口的每一个实现添加到容器中:操作系统
@Configuration public class EnvironmentApp { public static void main(String[] args) { //在启动容器以前,先指定环境中的profiles参数 System.setProperty("spring.profiles.active", "wow"); ApplicationContext ctx = new AnnotationConfigApplicationContext(EnvironmentApp.class); //当前的profile值是wow,因此获取的实现类是worldOfWarcraft Blizzard blizzard = ctx.getBean(Blizzard.class); } @Bean @Profile("war") public Blizzard warcraft() { return new Warcraft(); } @Bean @Profile("wow") public Blizzard worldOfWarcraft() { return new WorldOfWarcraft(); } @Bean @Profile("default") public Blizzard overwatch() { return new Overwatch(); } }
@Configuration类中每个@Bean注解以后都有一个@Profile注解。@Profile中的字符串就标记了当前适配的环境变量,他配合System.setProperty("spring.profiles.active", "wow");这一行一块儿使用。当设定环境参数为wow时,标记了@Profile("wow")的方法会被启用,对应的Bean会添加到容器中。而其余标记的Bean不会被添加,当没有适配到任何Profile值时,@Profile("default")标记的Bean会被启用。code
Spring Profile的功能就是根据在环境中指定参数的方法来控制@Bean的建立。
@Profile注解除了在@Bean方法上使用,也能够用于@Configuration类上。这样使用能够一次性控制多个Bean的加载。例以下面的例子:
@Configuration @Profile("cast") class CastConfig { @Bean public Castlevania castlevania() { return new Castlevania(); } } @Configuration @Profile("pes") class PESConfig { @Bean public ProEvolutionSoccer proEvolutionSoccer() { return new ProEvolutionSoccer(); } }
这样能够控制整个@Configuration类中的Bean是否加载。这个时候若是在@Configuration类上还标注了@Import注解,那么被@Import引入的类中的@Bean也不会添加到IoC容器中,那么这对统一配置环境是颇有好处的。
须要注意的是,若是这个时候又在@Bean之上添加了@Profile注解,那么Spring最终会根据@Bean之上的标签来执行。例如:
@Configuration @Profile("cast") class CastConfig { @Bean public Castlevania castlevania() { return new Castlevania(); } @Bean @Profile("pes") public ProEvolutionSoccer proEvolutionSoccer() { return new ProEvolutionSoccer(); } }
当环境中的profile值包含"pes"时候,@Profile("pes")标注的这个Bean就会添加到IoC容器中。
Profile特性也能够在XML配置。不过只能在<beans>标签上进行:
<beans ... > <beans profile="ff"> <bean class="chkui.springcore.example.hybrid.profile.service.squareenix.FinalFantasy" /> </beans> <beans profile="dog"> <bean class="chkui.springcore.example.hybrid.profile.service.squareenix.SleepingDogs" /> </beans> </beans>
配置以后,<beans>中的多个<bean>都会被Profile控制。
Profile的环境变量能够包含多个值。例如:
System.setProperty("spring.profiles.active", "wow,pes");
这样环境中就包含了2个Profile的值。对用的@Profile或profile配置就会被启用。
除了例子中给出的System::setProperty方法,Spring还提供了多种方法来设置Profile的环境变量。
-Dspring.profiles.active="wow,pes"
ConfigurableApplicationContext继承了ConfigurableEnvironment接口咱们能够经过ConfigurableEnvironment::getEnvironment方法获取到当前Spring中的环境对象——org.springframework.core.env.Environment,而后使用他来设置环境变量:
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(EnvironmentApp.class); ConfigurableEnvironment env = ctx.getEnvironment(); //经过setActiveProfiles来设置。 env.setActiveProfiles("wow","pes","ff"); //必须重建容器 ctx.refresh();
须要注意的是,在继承关系中ConfigurableApplicationContext以后才实现ConfigurableEnvironment,若是这里使用ApplicationContext::getEnvironment方法获得的是Environment,它不提供set相关的方法。因此上面的例子使用了ConfigurableApplicationContext。因为ApplicationContext的全部实现类都实现了Configurable的功能,咱们也能够像下面这样进行转型:
ApplicationContext ctx = new AnnotationConfigApplicationContext(EnvironmentApp.class); Environment _e =ctx.getEnvironment(); ConfigurableEnvironment env = ConfigurableEnvironment.class.cast(_e);
Profile特性的实现也不复杂,其实就是实现了Conditional功能(Conditional功能见@Configuration与混合使用一文中关于Conditionally的介绍)。
首先@Profile注解继承实现了@Conditional:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(ProfileCondition.class) public @interface Profile {}
而后他的处理类实现了Condition接口:
class ProfileCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles((String[]) value)) { return true; } } return false; } return true; } }
处理过程也很简单,实际上就检查@Profile注解中的值,若是和环境中的一致则添加。