目錄
3.1 環境與profile
數據庫配置、加密算法以及與外部系統的集成是跨環境部署時會發生變化的幾個典型例子
Spring引入了bean profile的功能。要使用profile,你首先要將所有不同的bean定義整理到一個或多個profile之中,在將應用部署到每個環境時,要確保對應的profile處於激活(active)的狀態。
- 在Java 中配置profile bean
在Java配置中,可以使用@Profile註解指定某個bean屬於哪一個profile。示例如下:
@Configuration
@Profile("dev")
Public class DevelopmentProfileConfig {
}
從Spring3.2開始,你也可以在方法級別上使用@Profile註解,與@Bean註解一同使用,這樣的話,就能將這兩個bean的聲明放到同一個配置類
- 在 XML 中配置profile bean
也可以通過<beans>元素的profile屬性,在XML中配置profile bean。
使用 XML 配置profile bean示例:
- 激活profile
Spring在確定哪個profile處於激活狀態時,需要依賴兩個獨立的屬性:spring.profiles.active和spring.profiles.default。如果設置了spring.profiles.active屬性的話,那麼它的值就會用來確定哪個profile是激活的。但如果沒有設置spring.profiles.active屬性的話,那Spring將會查找spring.profiles.default的值。如果spring.profiles.active和spring.profiles.default均沒有設置的話,那就沒有激活的profile,因此只會創建那些沒有定義在profile中的bean
有多種方式來設置這兩個屬性:
- 作爲DispatcherServlet的初始化參數;
- 作爲Web應用的上下文參數;
- 作爲JNDI條目;
- 作爲環境變量;
- 作爲JVM的系統屬性;
- 在集成測試類上,使用@ActiveProfiles註解設置。
示例:
3.2 條件化的 bean
是Spring4引入了一個新的@Conditional註解,它可以用到帶有@Bean註解的方法上。如果給定的條件計算結果爲true,就會創建這個bean,否則的話,這個bean會被忽略。
設置給@Conditional的類可以是任意實現了Condition接口的類型。可以看出來,這個接口實現起來很簡單直接,只需提供matches()方法的實現即可。如果matches()方法返回true,那麼就會創建帶有@Conditional註解的bean。如果matches()方法返回false,將不會創建這些bean。
matches()方法包含ConditionContext參數,通過ConditionContext,我們可以做到如下幾點:
- 藉助getRegistry()返回的BeanDefinitionRegistry檢查bean定義;
- 藉助getBeanFactory()返回的ConfigurableListableBeanFactory檢查bean是否存在,甚至探查bean的屬性;
- 藉助getEnvironment()返回的Environment檢查環境變量是否存在以及它的值是什麼;
- 讀取並探查getResourceLoader()返回的ResourceLoader所加載的資源;
- 藉助getClassLoader()返回的ClassLoader加載並檢查類是否存在。
3.3 處理自動裝配的歧義性
僅有一個bean匹配所需的結果時,自動裝配纔是有效的。如果不僅有一個bean能夠匹配結果的話,這種歧義性會阻礙Spring自動裝配屬性、構造器參數或方法參數
當確實發生歧義性的時候,Spring提供了多種可選方案來解決這樣的問題。你可以將可選bean中的某一個設爲首選(primary)的bean,或者使用限定符(qualifier)來幫助Spring將可選的bean的範圍
縮小到只有一個bean。
- 標示首選的bean
在聲明bean的時候,通過將其中一個可選的bean設置爲首選(primary)bean能夠避免自動裝配時的歧義性。
示例:
@Component
@Primary
Public class Ice CreamimplementsDessert
{...}
如果你使用XML配置bean的話,同樣可以實現這樣的功能。<bean>元素有一個primary屬性用來指定首選的bean
- 限定自動裝配的 bean
Spring的限定符能夠在所有可選的bean上進行縮小範圍的操作,最終能夠達到只有一個bean滿足所規定的限制條件。
@Qualifier註解是使用限定符的主要方式。它可以與@Autowired和@Inject協同使用,在注入的時候指定想要注入進去的是哪個bean。
Java不允許在同一個條目上重複出現相同類型的多個註解。
當你不想用@Qualifier註解的時候,可以類似地創建@Soft、@Crispy和@Fruity。通過在定義時添加@Qualifier註解,它們就具有了@Qualifier註解的特性。它們本身實際上就成爲了限定符註解。
3.4 bean 的作用域
在默認情況下,Spring應用上下文中所有bean都是作爲以單例(singleton)的形式創建的。也就是說,不管給定的一個bean被注入到其他bean多少次,每次所注入的都是同一個實例。
Spring定義了多種作用域,可以基於這些作用域創建bean,包括:
- 單例(Singleton):在整個應用中,只創建bean的一個實例。
- 原型(Prototype):每次注入或者通過Spring應用上下文獲取的時候,都會創建一個新的bean實例。
- 會話(Session):在Web應用中,爲每個會話創建一個bean實例。
- 請求(Rquest):在Web應用中,爲每個請求創建一個bean實例。
單例是默認的作用域,但是正如之前所述,對於易變的類型,這並不合適。如果選擇其他的作用域,要使用@Scope註解,它可以與@Component或@Bean一起使用
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Public Notepad notepad()
{
return new Notepad();
}
就購物車bean來說,會話作用域是最爲合適的,因爲它與給定的用戶關聯性最大。要指定會話作用域,我們可以使用@Scope註解,它的使用方式與指定原型作用域是相同的
@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
Public ShoppingCart cart()
{...}
Spring並不會將實際的ShoppingCart bean注入到StoreService中,Spring會注入一個到ShoppingCart bean的代理,如圖3.1所示。這個代理會暴露與ShoppingCart相同的方法,所以StoreService會認爲它就是一個購物車。但是,當StoreService調用ShoppingCart的方法時,代理會對其進行懶解析並將調用委託給會話作用域內真正的ShoppingCart bean
3.5 運行時植入
有時候硬編碼是可以的,但有的時候,我們可能會希望避免硬編碼值,而是想讓這些值在運行時再確定。爲了實現這些功能,Spring提供了兩種在運行時求值的方式:
- 屬性佔位符(Propertyplaceholder)。
- Spring表達式語言(SpEL)。
Spring一直支持將屬性定義到外部的屬性的文件中,並使用佔位符值將其插入到Springbean中。在Spring裝配中,佔位符的形式爲使用“${...}”包裝的屬性名稱。
如果我們依賴於組件掃描和自動裝配來創建和初始化應用組件的話,那麼就沒有指定佔位符的配置文件或類了。在這種情況下,我們可以使用@Value註解
public BlankDisc(@Value("${disc.title}") String title,@Value("${disc.artist}") String artist)
{
this.title=title;
this.artist=artist;
}
爲了使用佔位符,我們必須要配置一個PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean。從Spring3.1開始,推薦使用PropertySourcesPlaceholderConfigurer,因爲它能夠基於SpringEnvironment及其屬性源來解析佔位符
SpEL表達式要放到“#{...}”之中,這與屬性佔位符有些類似,屬性佔位符需要放到“${...}”之中。
SpEL表達式也可以引用其他的bean或其他bean的屬性。除了引用bean的屬性,我們還可以調用bean上的方法。
publicBlankDisc(@Value("#{systemProperties['disc.title']}") String title,@Value("#{systemProperties['disc.artist']}") String artist)
{
this.title=title;
this.artist=artist;
}
如果要在SpEL中訪問類作用域的方法和常量的話,要依賴T()這個關鍵的運算符