1、環境與profilejava
開發階段中,某些環境相關作法可能並不適合遷移到生產環境中,甚至即使遷移過去也沒法正常工做。數據庫配置、加密算法以及與外部系統的集成是跨環境部署時會發生變化的幾個典型例子。node
Spring並非在構建的時候作出這樣的決策,而是等到運行時再來肯定。在這個過程當中須要根據環境決定該建立哪一個bean和不建立哪一個bean。這樣的結果就是同一個部署單元(可能會是WAR文件)可以適用於全部的環境,沒有必要進行從新構建。正則表達式
Spring引入了bean profile的功能。要使用profile,你首先要將全部不一樣的bean定義整理到一個或多個profile之中,在將應用部署到每一個環境時,要確保對應的profile處於激活(active)的狀態。算法
在Java配置中,可使用@Profile註解指定某個bean屬於哪個profile。spring
1.1Javaconfig中配置sql
1 package com.wang.three; 2 3 import javax.sql.DataSource; 4 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Configuration; 7 import org.springframework.context.annotation.Profile; 8 import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 9 import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 10 11 @Configuration 12 @Profile("dev") 13 public class DevelopmentProfileConfig { 14 @Bean(destroyMethod="shutdown") 15 public DataSource dataSource(){ 16 return new EmbeddedDatabaseBuilder() 17 .setType(EmbeddedDatabaseType.H2) 18 .addScript("classpath:schema.sql") 19 .addScript("classpath:test-data.sql") 20 .build(); 21 } 22 23 }
註釋:@Profile註解應用在了類級別上。它會告訴Spring這個配置類中的bean只有在dev profile激活時纔會建立。若是dev profile沒有激活的話,那麼帶有@Bean註解的方法都會被忽略掉。數據庫
Spring 3.2開始,你也能夠在方法級別上使用@Profile註解,與@Bean註解一同使用。能夠在一個JavaConfig中配置多個profile數組
1 package com.wang.three; 2 3 import javax.sql.DataSource; 4 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Configuration; 7 import org.springframework.context.annotation.Profile; 8 import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 9 import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 10 import org.springframework.jndi.JndiObjectFactoryBean; 11 12 @Configuration 13 public class DatasourceConfig { 14 15 @Bean(destroyMethod="shutdown") 16 @Profile("dev") 17 public DataSource DevelopmentDataSource(){ 18 return new EmbeddedDatabaseBuilder() 19 .setType(EmbeddedDatabaseType.H2) 20 .addScript("classpath:schema.sql") 21 .addScript("classpath:test-data.sql") 22 .build(); 23 } 24 25 @Bean 26 @Profile("prod") 27 public DataSource ProductionDataSource(){ 28 JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean(); 29 jndiObjectFactoryBean.setJndiName("jdbc/myDs"); 30 jndiObjectFactoryBean.setResourceRef(true); 31 jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class); 32 return (DataSource)jndiObjectFactoryBean.getObject(); 33 } 34 }
儘管每一個DataSource bean都被聲明在一個profile中,而且只有當規定的profile激活時,相應的bean纔會被建立,可是可能會有其餘的bean並無聲明在一個給定的profile範圍內。沒有指定profile的bean始終都會被建立,與激活哪一個profile沒有關係。安全
1.2xml中配置session
全部的配置文件都會放到部署單元之中(如WAR文件),可是隻有profile屬性與當前激活profile相匹配的配置文件纔會被用到。
profile="dev"> 也能夠新鍵其餘的配置文件
<?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:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" profile="dev"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:schema.sql"/> <jdbc:script location="classpath:test-data.sql"/> </jdbc:embedded-database> </beans>
還能夠在根<beans>元素中嵌套定義<beans>元素,而不是爲每一個環境都建立一個profile XML文件。這可以將全部的profile bean定義放到同一個XML文件中
<?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:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <beans profile="dev"> <jdbc:embedded-database id="dataSource" type="H2"> <jdbc:script location="classpath:schema.sql" /> <jdbc:script location="classpath:test-data.sql" /> </jdbc:embedded-database> </beans> <beans profile="prod"> <jee:jndi-lookup id="dataSource" lazy-init="true" jndi-name="jdbc/myDatabase" resource-ref="true" proxy-interface="javax.sql.DataSource" /> </beans> </beans>
有兩個bean,類型都是javax.sql.DataSource,而且ID都是dataSource。可是在運行時,只會建立一個bean,這取決於處於激活狀態的是哪一個profile
1.3激活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的參數將spring.profiles.default設置爲開發環境的profile,我會在Servlet上下文中進行設置(爲了兼顧到ContextLoaderListener)。
當應用程序部署到QA、生產或其餘環境之中時,負責部署的人根據狀況使用系統屬性、環境變量或JNDI設置spring.profiles.active便可。
在spring.profiles.active和spring.profiles.default中,profile使用的都是複數形式。這意味着你能夠同時激活多個profile,這能夠經過列出多個profile名稱,並以逗號分隔來實現。
Spring提供了@ActiveProfiles註解,咱們可使用它來指定運行測試時要激活哪一個profile。
(@ActiveProfiles("dev"))
2、條件化的bean
Spring 4引入了一個新的@Conditional註解,它能夠用到帶有@Bean註解的方法上。若是給定的條件計算結果爲true,就會建立這個bean,不然的話,這個bean會被忽略。
舉例:假設有一個名爲MagicBean的類,咱們但願只有設置了magic環境屬性的時候,Spring纔會實例化這個類。若是環境中沒有這個屬性,那麼MagicBean將會被忽略。
//MagicBean package com.wang.three;
public class MagicBean { }
使用@Conditional註解條件化地配置了MagicBean。
package com.wang.three; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @Configuration public class MagicConfig { @Conditional(MagicExistsCondition.class) public MagicBean magicBean(){ return new MagicBean(); } }
設置給@Conditional的類(例如MagicExistsCondition)能夠是任意實現了Condition接口的類型(這個接口是Spring中的接口)。
import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; public class MagicExistsCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // TODO Auto-generated method stub Environment env = context.getEnvironment(); return env.containsProperty("magic"); } }
這個接口實現起來很簡單直接,只需提供matches()方法的實現便可。若是matches()方法返回true,那麼就會建立帶有@Conditional註解的bean。若是matches()方法返回false,將不會建立這些bean。
它經過給定的ConditionContext對象進而獲得Environment對象,並使用這個對象檢查環境中是否存在名爲magic的環境屬性。只要屬性存在便可知足要求。若是知足這個條件的話,matches()方法就會返回true。所帶來的結果就是條件可以獲得知足,全部@Conditional註解上引用MagicExistsCondition的bean都會被建立。matches()方法會獲得ConditionContext和AnnotatedTypeMetadata對象用來作出決策。
ConditionContext是一個接口:
經過ConditionContext,咱們能夠作到以下幾點:
一、藉助getRegistry()返回的BeanDefinitionRegistry檢查bean定義;
二、藉助getBeanFactory()返回的ConfigurableListableBeanFactory檢查bean是否存在,甚至探查bean的屬性;
三、藉助getEnvironment()返回的Environment檢查環境變量是否存在以及它的值是什麼;
四、讀取並探查getResourceLoader()返回的ResourceLoader所加載的資源;
五、藉助getClassLoader()返回的ClassLoader加載並檢查類是否存在。
AnnotatedTypeMetadata也是一個接口,可以讓咱們檢查帶有@Bean註解的方法上還有什麼其餘的註解。
藉助isAnnotated()方法,咱們可以判斷帶有@Bean註解的方法是否是還有其餘特定的註解。藉助其餘的那些方法,咱們可以檢查@Bean註解的方法上其餘註解的屬性。
@Profile註解是基於@Conditional和Condition實現。
3、處理自動裝配bean產生的歧義
自動裝配歧義的產生:
Dessert是一個接口,而且有三個類實現了這個接口,分別爲Cake、Cookies和IceCream:
@Component public class Cake implements Dessert { } @Component public class Cookies implements Dessert { } @Component public class IceCream implements Dessert { }
在自動化裝配時,
@Autowired public void setDessert(Dessert dessert){ this.dessert=dessert; }
由於這三個實現均使用了@Component註解,在組件掃描的時候,可以發現它們並將其建立爲Spring應用上下文裏面的bean。而後,當Spring試圖自動裝配setDessert()中的Dessert參數時,它並無
惟1、無歧義的可選值。以後會發生異常(NoUniqueBeanDefinitionException)。
解決方案:
3.1首選的bean
在Spring中,能夠經過@Primary來表達最喜歡的方案。
@Primary可以與@Component組合用在組件掃描的bean上
import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component @Primary public class IceCream implements Dessert { }
@Primary與@Bean組合用在Java配置的bean聲明中
@Bean @Primary public Dessert iceCream(){ return new IceCream(); }
用XML配置bean,<bean>元素有一個primary屬性用來指定首選的bean
注意:若是你標示了兩個或更多的首選bean,那麼它就沒法正常工做了
3.2限定自動裝配的bean
@Qualifier註解是使用限定符的主要方式。它能夠與@Autowired和@Inject協同使用。
3.2.1 使用默認的限定符:若是沒有指定其餘的限定符的話,全部的bean都會給定一個默認的限定符,這個限定符與bean的ID相同。
@Autowired @Qualifier("iceCream") public Dessert setDessert(Dessert dessert){ this.dessert = dessert; }
3.2.2建立自定義的限定符
自定義了限制符的名稱
@Component @Qualifier("cold") public class IceCream implements Dessert { }
進行引用
@Autowired @Qualifier("cold") public Dessert setDessert(Dessert dessert){ this.dessert= dessert; }
注意:當使用自定義的@Qualifier值時,最佳實踐是爲bean選擇特徵性或描述性的術語,而不是使用隨意的名字。
3.2.3自定義的限定符註解
若是多個bean都具有相同特性的話,這種作法也會出現重匹配的問題。(能夠用多組描述區分)可是因爲Java中不允許相同註解的出現,故須要自定義註解
建立自定義的限定符註解,藉助這樣的註解來表達bean所但願限定的特性。這裏所須要作的就是建立一個註解,它自己要使用@Qualifier註解來標註。這樣咱們將再也不使用@Qualifier("cold"),而是使用自定義的@Cold註解。其用法和@Qualifier同樣
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD,ElementType.CONSTRUCTOR}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Cold { }
4、bean的做用域
Spring定義了多種做用域,能夠基於這些做用域建立bean,包括:
單例(Singleton):在整個應用中,只建立bean的一個實例。
原型(Prototype):每次注入或者經過Spring應用上下文獲取的時候,都會建立一個新的bean實例。
會話(Session):在Web應用中,爲每一個會話建立一個bean實例。
請求(Rquest):在Web應用中,爲每一個請求建立一個bean實例。
在默認狀況下,Spring應用上下文中全部bean都是做爲以單例(singleton)的形式建立的。也就是說,無論給定的一個bean被注入到其餘bean多少次,每次所注入的都是同一個實例。
若是選擇其餘的做用域,要使用@Scope註解,它能夠與@Component或@Bean一塊兒使用。
4.1原型
使用組件掃描來發現和聲明bean,那麼你能夠在bean的類上使用@Scope註解,將其聲明爲原型bean:
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//或爲(@Scope("prototype")) public class Notepad { // the details of this class are inconsequential to this example }
想在Java配置中將Notepad聲明爲原型bean,那麼能夠組合使用@Scope和@Bean來指定所需的做用域:
@Bean @Scope("prototype") public NodePad nodePad(){ return new NodePad(); }
使用XML來配置bean的話,可使用<bean>元素的scope屬性來設置做用域:
4.2會話與請求域
就購物車bean來講,會話做用域是最爲合適的,由於它與給定的用戶關聯性最大。
將value設置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。這會告訴Spring爲Web應用中的每一個會話建立一個ShoppingCart。這會建立多個ShoppingCart bean的實例,可是對於給定的會話只會建立一個實例,在當前會話相關的操做中,這個bean實際上至關於單例的。
對於:proxyMode屬性,它被設置成了ScopedProxyMode.INTERFACES。這個屬性解決了將會話或請求做用域的bean注入到單例bean中所遇到的問題。
例如當把其注入到一個單例中
Spring並不會將實際的ShoppingCart bean注入到StoreService中,Spring會注入一個到ShoppingCart bean的代理,這個代理會暴露與ShoppingCart相同的方法,因此StoreService會認爲它就是一個購物車。可是,當StoreService調用ShoppingCart的方法時,代理會對其進行懶解析並將調用委託給會話做用域內真正的ShoppingCart bean。
proxyMode屬性被設置成了ScopedProxyMode.INTERFACES,這代表這個代理要實現ShoppingCart接口,並將調用委託給實現bean。
若是ShoppingCart是接口而不是類的話,這是能夠的(也是最爲理想的代理模式)。但若是ShoppingCart是一個具體的類的話,Spring就沒有辦法建立基於接口的代理了。此時,它必須使用CGLib來生成基於類的代理。因此,若是bean類型是具體類的話,咱們必需要將proxyMode屬性設置爲ScopedProxyMode.TARGET_CLASS,以此來代表要以生成目標類擴展的方式建立代理。
請求做用域與之相似。
4.3在xml中聲明做用域及代理
使用XML來聲明會話或請求做用域的bean,那麼就使用<bean>元素的scope屬性;要設置代理模式,須要使用Spring aop命名空間的一個新元素。
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="cart" class="com.wang.three.ShoppingCart" scope="session"> <aop:scoped-proxy/> </bean> </beans>
<aop:scoped-proxy>是與@Scope註解的proxyMode屬性功能相同的Spring XML配置元素。它會告訴Spring爲bean建立一個做用域代理。默認狀況下,它會使用CGLib建立目標類的代理。可是咱們也能夠將proxy-target-class屬性設置爲false,進而要求它生成基於接口的代理:
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="cart" class="com.wang.three.ShoppingCart" scope="session"> <aop:scoped-proxy proxy-target-class="false"/> </bean> </beans>
5、運行時植入
當望避免硬編碼值,而是想讓這些值在運行時再肯定。Spring提供了兩種在運行時求值的方式:
屬性佔位符(Property placeholder)。
Spring表達式語言(SpEL)。
5.1注入外部值
在Spring中,處理外部值的最簡單方式就是聲明屬性源並經過Spring的Environment來檢索屬性。
@Configuration @PropertySource("classpath:/app.properties")//屬性源 public class ExpressiveConfig { @Autowired Environment env; @Bean public BlankDisc disc(){ return new BlankDisc(env.getProperty("disc.title"),env.getProperty("disc.artist")); } }
5.1.1學習Spring的Environment
Spring中使用getProperty(),有四個重載
a、String getProperty(String key)
b、String getProperty(String key,String defaultValue)
c、T getProperty(String key,Class<T> type)
d、T getProperty(String key,Class<T> type, T defaultValue)
使用a、b都會返回的是一個String類型的值,可是b會在指定的屬性key不存在的時候返回一個默認的值。
而對於它們不會將全部的值都視爲String類型,而是根據type來肯定。
int connectionCount = env.getProperty("db.connection.count",Interger.class,30);
若是disc.title或disc.artist屬性沒有定義的話且沒有設置默認值,將會拋出IllegalStateException異常。
a、能夠調用Environment的containsProperty()方法:檢查一下某個屬性是否存在的話。
boolean titleExists = env.containsProperty("disc.title");
b、將屬性解析爲類
自定義的CompactDisc的類
Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class", CompactDisc.class);
c、檢驗哪些profile處於激活狀態
String[] getActiveProfiles():返回激活profile名稱的數組;
String[] getDefaultProfiles():返回默認profile名稱的數組;
boolean acceptsProfiles(String... profiles):如environment支持給定profile的話,就返回true。
(實際的例子沒有寫,詳情看書91頁和78頁)
5.1.2佔位符
Spring一直支持將屬性定義到外部的屬性的文件中,並使用佔位符值將其插入到Spring bean中。在Spring裝配中,佔位符的形式爲使用「${... }」包裝的屬性名稱。
做爲樣例,咱們能夠在XML中按照以下的方式解析BlankDisc構造器參數:
<bean id="sgtPeppers" class="com.wang.second.BlankDisc" c:_title="${disc.title}" c:_artist="${disc.artist}" />
依賴於組件掃描和自動裝配來建立和初始化應用組件的話,那麼就沒有指定佔位符的配置文件或類了。在這種狀況下,咱們可使用@Value註解。
public BlankDisc(@Value("${disc.title}") String title,@Value("${disc.artist}")String artist){ this.title=title; this.artist=artist; }
解析配置的佔位符:
爲了使用佔位符,咱們必需要配置一個PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean。
推薦使用PropertySourcesPlaceholderConfigurer,由於它可以基於Spring Environment及其屬性源來解析佔位符。
使用@bean方法在Java中配置了PropertySourcesPlaceholderConfigurer
@Bean public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){ return new PropertySourcesPlaceholderConfigurer(); }
在xml中也能夠配置,Spring context命名空間中的<context:propertyplaceholder>元素將會爲你生成PropertySourcesPlaceholderConfigurer bean:
總之:解析外部屬性可以將值的處理推遲到運行時,可是它的關注點在於根據名稱解析來自於Spring Environment和屬性源的屬性。
5.2強大的SpEL(Spring 表達式語言)
能夠實現外部注入等等,可以應用在DI以外的其餘地方
簡介:以一種強大和簡潔的方式將值裝配到bean屬性和構造器參數中,在這個過程當中所使用的表達式會在運行時計算獲得值
SpEL擁有不少特性,包括:
使用bean的ID來引用bean;
調用方法和訪問對象的屬性;
對值進行算術、關係和邏輯運算;
正則表達式匹配;
集合操做
a、SpEL表達式要放到「#{ ... }」之中。
b、T()表達式
若是要在SpEL中訪問類做用域的方法和常量的話,要依賴T()這個關鍵的運算符。
#{T(Syste).currentTimeMillis()}//會將java.lang.System視爲Java中對應的類型
c、systemProperties對象引用系統屬性
如:#{systemProperties('disc.title')}
相似屬性佔位符的例子,組件掃面建立bean
public BlankDisc(@Value("#{systemProperties['disc.title']}") String title,@Value("#{systemProperties['disc.artist']}")String artist){ this.title=title; this.artist=artist; }
在XML配置中,你能夠將SpEL表達式傳入<property>或<constructor-arg>的value屬性中,或者將其做爲p-命名空間或c-命名空間條目的值。例如,在以下BlankDisc bean的XML聲明中,構造器參數就是經過SpEL表達式設置的:
d、字面值
整數、浮點數、String值和Boolean
#{1}、#{1.2}、#{9.87E3}、#{true}
f、引用bean、屬性和方法
經過ID引用其餘的bean 例:#{SgtPeppers}
或其屬性#{SgtPeppers.artist}
或調用bean中方法,對於被調用方法的返回值來講,咱們一樣能夠調用它的方法 #{artSelect.selectArtist().toUpperCase()} 。
爲了安全起見:使用了「?.」運算符。這個運算符可以在訪問它右邊的內容以前,確保它所對應的元素不是null。
#{artSelect.selectArtist()?.toUpperCase()}
e、SqEL運算符
運算符類型 | 運算符 |
算數運算符 | +、-、 * 、/、%、^ |
比較運算符 | < 、 > 、 == 、 <= 、 >= 、 lt 、 gt 、 eq 、 le 、 ge |
邏輯運算符 | and 、 or 、 not 、│ |
條件運算符 | ?: (ternary) 、 ?: (Elvis) |
正則表達式 | matches |
三元運算符的一個常見場景就是檢查null值,並用一個默認值來替代null。例如,以下的表達式會判斷disc.title的值是否是null,若是是null的話,那麼表達式的計算結果就會是「Rattleand Hum」:
f、正則表達式
g、計算集合
要從jukebox中隨機選擇一首歌:
取字符串的字符
查詢運算符(.?[]),它會用來對集合進行過濾,獲得集合的一個子集
//但願獲得jukebox中artist屬性爲Aerosmith的全部歌曲
「.^[]」和「.$[]」,它們分別用來在集合中查詢第一個匹配項和最後一個匹配項。
投影運算符(.![]),它會從集合的每一個成員中選擇特定的屬性放到另一個集合中。
//將title屬性投影到一個新的String類型的集合中