在介紹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註解中的值,若是和環境中的一致則添加。