Spring in Action --- 第三章 高級裝配

Spring profile

使用Spring profile 能夠設定在不一樣的環境中啓用不一樣的bean.因爲環境的不一樣,數據庫配置,加密算法以及與外部系統的集成可能會有不一樣的表現,特別是數據庫,通常開發環境,QA環境,預發佈環境,生產環境會分開java

Spring並非在構建的時候作出這樣的決策的,而是等到運行時再來肯定web

在java中

使用@Porfile註解指定某個bean屬於哪個profile,算法

@Profile("dev")

表示profile環境是dev.spring

只有相應的profile激活的時候,纔會建立對應的bean,可是,沒有指定profile的bean始終都會被建立,與激活哪一個profile沒有關係數據庫

在XML中

<beans xmlns="http://springframework.org/schema/beans"
        xmlns:...
 profile="dev">
 </beans>

建立不一樣的XML文件設置不一樣的profile,也能夠在根<bean>元素中嵌套定義<beans>元素,而不是爲每個環境建立一個profile XML文件.app

激活 profile

Spring在肯定哪一個profile處於激活狀態時,須要依賴兩個獨立的屬性:spring.profiles.activespring.profiles.default,若是設置了 spring.profiles.active 屬性,那麼它的值就會用來肯定哪一個profile是激活的,可是若是沒有設置,那 Spring會查找spring.profiles.defalut的值.若是二者均爲設置,那麼就沒有激活的profile,Spring只會建立哪些沒有定義在profile中的bean.oop

設置屬性的方式
  • 做爲 DispatcherServlet 的初始化參數
  • 做爲 Web 應用的上下文參數
  • 做爲 JNDI 條目
  • 做爲環境變量
  • 做爲JVM的系統屬性

示例:
在web.xml中爲上下文設置默認的profile測試

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

在web.xml中爲Servlet設置默認的profileui

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>
        prg.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
    </init-param>
</servlet>

使用profile進行測試

Spring提供了@ActiveProfiles註解指定運行測試時要激活哪一個profile.this

條件化的 bean

Spring4之後,引入了@Conditional,能夠用到帶有@Bean註解的方法上,用於計算給定的條件若是爲true,就會建立這個bean,不然的話這個bean會被忽略
例若有一個Test的類,咱們但願只有設置了 test 環境屬性的時候,Spring纔會實例化這個類,若是沒有,就會被忽略.下面的配置展現了註解的使用方式

@Bean
@Conditional(TestExistsCondition.class)
public Test test() {
    return new Test();
}

@Condition中給定了一個Class,指明建立該bean的條件,設置給它的類能夠是任意實現了Condition接口的類型.下面展示了TestExistsCondition類的實現

public class TestExistsCondition() {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    Environment env = context.getEnvironment();
    return env.containsProperty("test");
    }
}

其中的兩個參數 ConditionContext, AnnotatedTypeMetadata 都是接口,具體請看P77

處理自動裝配的歧義性

在使用自動裝配時,若是一個接口有多個實現類而且都被定義爲bean,當Spring試圖裝配的時候就會沒法作出選擇從而拋出 NoUniqueBeanDefinitionException, Spring提供了兩種方式來解決這樣的問題:

  • 將可選bean的某一個設置爲首選的bean
  • 使用限定符(qualifier)來幫助Spring將可選的bean的範圍縮小到只有一個bean

標示首選的bean

在 bean 上使用@Primary註解標示當前的bean是首選bean,若是你是用XML來實例化bean
,能夠用下面的方式:

<bean id="iceCream" class="com.IceCream" primary="true">
</bean>

可是,若是你設置了兩個或者更多的首選bean,那麼它就沒法正常工做了

限定自動裝配的bean

@Qualifier註解是使用限定符的主要方式:

@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
    this.dessert = dessert
}

上面的代碼片斷是最簡單的例子,爲@Qualifier註解所設置的參數就是想要注入的bean的ID,全部的@Component註解建立的類的ID都是首字母變爲小寫的類名.須要注意的是,若是沒有指定其餘的限定符的話,全部的bean都會給定一個默認的限定符,這個限定符與bean的ID相同

這裏有個問題就是,指定的限定符與要注入的bean的名稱是緊耦合的,若是bean的名字修改了,那麼就會沒法注入,解決方案是在bean上使用@qualifier指定該bean的限定符

使用自定義的限定符註解

若是iceCream同時被使用在兩個或更多的bean上,那怎麼辦呢?你可能會想到的是使用更多的限定符來縮小範圍:

@Component
@Qualifier("iceCream")
@Qualifier("cold")
public class IceCream implements Dessert {...}

這裏只有一個小問題,Java不容許在同一個條目上重複出現相同類型的多個註解.若是你這麼作了,編譯將會報錯.可是,咱們能夠建立自定義的限定符註解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Qualifier
public @interface Cold{}

上面的代碼片斷定義了一個自定義註解,它自己要使用@Qualifier來標註,這樣就能夠不斷縮小範圍,直到惟一.

bean 的做用域

在默認狀況下,Spring應用上下文中全部的bean都是做爲以單利的形式建立的,不過,Spring提供了多種做用域,能夠基於這些做用域建立bean:

  • 單例:在整個應用中,只建立bean的一個實例
  • 原型:每次注入或者經過Spring應用上下文獲取的時候,都會建立一個新的bean實例
  • 會話:在Web應用中,爲每一個會話建立一個bean實例
  • 請求:在Web應用中,爲每一個請求建立一個bean實例

默認是單例模式,若是選擇其餘的做用域,要使用@Scope註解,例如,要將一個bean生命爲原型,須要這樣作:

@bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad{...}

使用組件掃描來發現和聲明bean,只須要加上@Component註解替換@Bean,一樣,若是使用XML來配置bean的話,可使用scope屬性來設置做用域:

<bean id="notepad" class ="com.notepad" scope="prototype" />

對於會話做用域,在當前會話的相關操做中,bean其實是單例的:

@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() {...}

要注意的是,同時還有一個proxyMode屬性被設置,請看下面的代碼:

@Component
public class StoreService {
    @Autowired
    public void setShoppingCart(ShoppingCart shoopingCart) {
        this.shoppingCart = shoppingCart
    }
}

這個service是一個單例的bean,當它建立的時候,Spring會試圖將ShoppingCart bean注入,可是ShoppingCart是會話做用域的,此時並不存在,知道某個用戶進入到系統纔會出現實例,另外,系統中將會有多個ShoppingCart實例被建立,咱們但願注入的bean恰好是當前會話對應的那一個,所以,Spring會向StroeService中注入一個到ShoppingCart的代理.關於具體的描述,請看P87

運行時注入

在代碼中,最好的方式是不要在代碼中出現硬代碼,所以咱們須要藉助Spring的一些配置來注入外部的值:

@Configuration
@PropertySource("")
public class ExpressiveConfig {
    @Autowired
    Environment env;
    
    @Bean
    public BlankDisc disc() {
        return new BlankDisc(env.getProperty("disc.title"));
    }
}

咱們使用了@ PropertySource註解引用了外部的一個屬性文件,其中有disc.title屬性,會被讀取並配置到bean中,若是這個屬性沒有被定義,獲取到的值會是null,若是你但願這個屬性必須被定義,可使用getRequiredProperty()方法.

另外,可使用Environment.containsProperty()方法來判斷是否有某一個屬性.最後,若是想將屬性解析爲類的話,可使用getPropertyAdClass()方法.

若是咱們依賴組件掃描和自動裝配來建立和初始化應用組件的話,娜美可使用@Value註解,它與@Autowired很是類似:

public BlankDisc(@Value("${disc.title}") String title) {
    this.title = title;
}

爲了使用佔位符,咱們必需要配置一個PropertyPlaceHolderConfigurer bean或 PropertySourcePlaceHolderConfigurer bean,具體請看P92

註解

@Profile

Spring 3.2之後,註解支持在類和方法級別上使用
@Profile("...")括號中能夠定義任意的字符串,但建議用有意義的字符串

@ActiveProfiles

@Conditional

@Primary

@Qualifier

@Scope

@PropertySource

相關文章
相關標籤/搜索