Spring IOC學習(02)Bean的高級裝配 1. 環境與profile 2. 條件化的bean 3. 自動裝配的歧義性 4. Bean的作用域 5. 運行時值注入 6. 總結

內容概覽


    1. 環境與profile
    1. 條件化的bean
    1. 自動裝配的歧義性
    1. Bean的作用域
    1. 運行時值注入
    1. 總結

1. 環境與profile


在項目開發的過程中,不可避免的一個問題是項目需要在不同的環境之間運行與切換,比如最經典的例子,通常大部分中小型的項目分爲開發(dev),測試(test),生產(prod)三個環境,這三個環境中,配置的數據庫,緩存,常量或者祕鑰等內容非常有可能是不同的,環境的切換通常非常有可能引發非預期的問題,詳細大家都深有體會。

這種多環境的問題,參數佈局最合理的方案就是,將不同環境的參數配置到不同的互不影響的文件中,然後在一個總的配置中引用指定環境的配置,這樣進行環境切換時,只需要修改指定引用即可,所幸Spring在這方面的配置上給了非常簡潔的方案。只要使用哪一個環境的配置,就激活哪一個就可以。

下面通過非常簡單的代碼來看一個多環境的例子,首先來看JavaConfig配置類中配置多環境,我們首先來配置一個bean,這個bean屬於開發環境:

上面的配置類中使用了一個註解@Profile,這個註解就是配置環境的註解,這個註解需要一個參數,參數值就是環境的名字,上面配置的就是開發環境的bean,下面來看測試環境:

注意註解@Profile現在是配置在類級別上的,它表示整個類中的bean都是屬於這個環境下創建,其實該註解還可以加在方法級別上,這樣就能將多個環境的bean放在同一個配置類中,

注意,指定環境的bean會在該環境被激活時才創建,那些沒有指定環境的bean就表示是否創建與激活哪個環境都沒有關係,所以始終都會被創建。

上面是在Java配置類中配置,下面來看在xml文件中配置環境,首先看一個整個文件都爲開發環境的xml:

注意,我們在開始的beans標籤的結尾加了一個 profile="dev" ,這樣就能指定爲dev環境。測試環境也是一樣:

上面兩個xml都是整體文件屬於一個環境,其實還可以在同一個文件中配置多個環境的bean,只需要在根元素beans下面繼續嵌套beans標籤即可:

上面的例子在JavaConfig和xml兩種文件中通過兩種方式定義不同的項目運行環境中的bean,那麼問題來了,如何激活並使用某個profile呢?

Spring在確定哪個profile處於激活狀態時,需要依賴兩個獨立的屬性:spring.profiles.active和spring.profiles.default,通過單詞意思大概也可以理解這兩個配置的含義,一個是主動激活,一個是默認值,如果設置了spring.profiles.active屬性的話,那麼它設置的值就是被激活的值,但是如果沒有設置spring.profiles.active,Spring將會查找spring.profiles.default的值。如果兩個都沒有設置,那就表示沒有任何環境被激活,因此只會創建那些沒有指定profile的bean。有多種方式來設置這兩種屬性:

  • 作爲 DispatcherServlet的初始化參數
  • 作爲Web應用的上下文參數
  • 作爲JNDI條目
  • 作爲環境變量
  • 作爲JVM的系統屬性
  • 在集成測試類上,使用@ActiveProfiles註解設置

舉個栗子,比如使用DispatcherServlet的參數將spring.profiles.default的值設置爲dev開發環境,在Servlet上下文中進行設置(爲了兼顧到ContextLoaderListener)。例如在web應用中,在web.xml文件中的設置如下:

這樣設置在開發環境中,就能直接啓動激活dev環境,不需要額外的配置。當應用程序部署到生產環境或者其它環境時,運維人員可以根據情況使用JVM系統屬性或者配置環境變量或者JNDI設置等方式設置spring.profile.active即可。這樣spring.profiles.default的值就無所謂了,不影響生產環境。系統會優先使用spring.profiles.active的值。

上面spring.profiles.*兩個參數中,profiles都是複數形式,表示可以同時激活多個profile,以逗號間隔,當然設置的多個環境要沒有衝突,否則沒有意義。

最後來看下在測試的時候配置環境,只需要使用@ActiveProfiles註解即可,參數是一個或者多個環境名:

2. 條件化的bean


前面討論的profile其實就是一種條件化創建,在滿足一定條件,即當前環境處於激活狀態,那麼某些bean就會被創建。關於條件化創建bean,還可以使用註解 @Conditional 來實現。首先我們來定義一個普通類:

然後就可以將此類配置成一個bean,但是是有條件的,如果實現這個條件邏輯呢?我們需要創建一個條件類 User013Condition ,此類需要實現 Condition 接口:

這個接口需要我們實現一個 matches方法,這個方法返回true,就表示滿足條件,可以創建bean,返回false就不能創建。matches方法很簡單,但是功能強大,它有兩個參數,第一個是ConditionContext類型參數,第二個是AnnotatedTypeMetadata類型參數。先來看第一個,ConditionContext是一個接口,我們來看一下這個接口:

其中:

  • getRegistry() 方法返回的 BeanDefinitionRegistry 可以檢查bean的定義
  • getBeanFactory() 方法返回的 ConfigurableListableBeanFactory 可以檢查bean是否存在,甚至探查bean的屬性
  • getEnvironment() 方法返回的 Environment 可以檢查環境變量是否存在,以及它的值是什麼
  • getResourceLoader() 方法 可以讀取並探查 ResourceLoader 所加載的資源
  • getClassLoader()方法 返回的 ClassLoader 可以加載並檢查類是否存在

通過上面幾個方法可以看到,ConditionContext參數十分強大,幾乎可以獲取spring項目中大部分的項目信息。第二個AnnotatedTypeMetadata類型參數能夠讓我們檢查帶有@Bean註解的方法上還有什麼其它註解,AnnotatedTypeMetadata也是一個接口,具體內容如下:

其中藉助 isAnnotated 方法,我們能夠判斷帶有@Bean註解的方法是不是還有其它特定的註解,藉助其它幾個方法,我們能夠檢查帶有@Bean註解的方法上其它註解的屬性。我們來看一下@Profile:

可以發現,這個註解的功能也是通過@Conditional註解實現的,這是spring4版本對@Profile進行了重構,我們來看一下具體實現邏輯:

可以看到這這個類中,通過AnnotatedTypeMetadata得到了用於@Profile註解的所有屬性,藉助該信息,它會明確檢查value值,該屬性包含了bean的profile名稱。然後它根據ConditionContext得到Environment 來檢查該profile (藉助acceptsProfiles方法) 是否處於激活狀態。

下面我們來實現一個自己的邏輯,檢查環境變量中是否含有ioc,如果有,就創建該bean,

然後再配置類中配置Bean:

3. 自動裝配的歧義性


前面的內容我們已經討論過自動化裝配,不過自動裝配有歧義性,如果自動裝配時,不止有一個bean能夠匹配結果的話,這種歧義性會阻礙Spring自動裝配屬性、構造器參數或方法參數。

舉個栗子,我們使用自動裝配:

然後,寫一個接口,這個接口有兩個實現類,都是bean,

當我們把這個bean裝配到其它bean中時,卻沒有指明是哪個具體bean,而是使用接口類型:

我們來調用:

這時候,Spring無法做出選擇,別無他法,只好宣佈失敗,跑出NoUniqueBeanDefinitionException異常:

這只是一個簡單的例子,實際開發中,歧義性確實非常罕見,不過確實是一個問題。當確實發生歧義性時,Spring提供了多種可選方案來解決這樣的問題,你可以將多個可選bean中的某一個設置爲首選(primary)bean,或者使用限定符(qualifier)來幫助Spring將可選的bean的範圍縮小到只有唯一一個。

來看一下第一種方案,使用primary來標記一個首選的bean,可以使用@Primary註解配合@Component註解使用,將其中一個bean標誌位首選:

這是自動裝配的情況,如果是顯式配置,可以和@Bean註解配合使用,標記在方法上來設置首選bean,如果是xml配置,可以在bean標籤內增加一個屬性 primary="true" 來標記哪個是首選bean。這樣在運行的時候,Spring就不會有歧義性。但是要注意首選bean不能標記多個,標記多個和沒有標記效果一樣,都會產生歧義。

上面是第一種方案,更加強大的是第二種方案,使用限定符。設置首選bean的侷限性在於,無法將可選方案的範圍限定到唯一一個無歧義性的選項中。它只能標識一個優先的可選方案,當首先bean的數量超過一個時,我們並沒有其它方法進一步縮小可選範圍。相反,限定符能夠在所有可選方案中進一步縮小範圍,最終能夠達到只有一個bean滿足所規定的限定條件,如果將現有的所有限定符使用上依然存在歧義性,那麼你可以繼續使用更多的限定符來繼續縮小範圍。

@Qualifier註解是使用限定符的主要方式,它可以與@Autowire等注入註解協同使用,在注入的時候指定想要使用的是哪個bean,例如:

這是使用限定符最簡單的例子,@Qualifier註解的參數就是所選bean的id,我們前面討論過,每個bean都有一個默認的id,就是類名,不過首字母要變爲小寫。

其實更準確的來說,@Qualifier註解的參數是一個String類型的限定符,每個bean都會指定一個限定符,如果沒有顯式指定,那麼這個限定符和bean的id相同。

在沒有顯式的指明限定符的時候,會存在問題,如果類名修改了,那麼限定符會跟着修改,所以更好的辦法是手動指明一個限定符,這樣就不會受其它地方的修改產生影響。例如:

同樣,使用註解@Qualifier指定限定符還可以配置在帶有@Bean註解的方法上。不過如果限定符有重複的情況,比如:

當然這種情況是極少的,遇到這種情況我們可以增加限定符來繼續縮小範圍:

報錯了,這時候遇到了一個問題,Java不允許在同一個條目上重複出現相同類型的多個註解,這裏可以採取一個折中的辦法,就是自定義註解,比如根據 @Qualifier("nums"),@Qualifier("one"),@Qualifier("two")三個標識,我們可以自定義下面三個註解:

這樣我們就可以把新定義的註解標記在原來有@Qualifier的地方:

通過自定義的限定符註解,我們就可以同時使用多個限定符,不會再報錯了。相對於原來的@Qualifier註解,自定義限定符註解類型更加安全。

4. Bean的作用域


在默認情況下,Spring應用上下文中所有的bean都是以單例(singleton)的形式創建的。也就是說不管一個bean被注入到其它bean多少次,每次所注入的都是同一個實例,當然大多數情況下面,單例bean是理想的方案。

但是有的時候,某些bean是有一定狀態的,它是易變的,因此重用就是不安全的,再使用單例就不合適了。在這方面,Spring定義了多種作用域,可以基於這些作用域創建bean,包括:

  • 單例(Singletone):在整個應用中,只創建一個bean的實例
  • 原型(Prototype):每次注入或者通過Spring上下文獲取時,都會創建一下新的bean實例
  • 會話(Session):在web應用中,爲每個會話創建一個bean實例
  • 請求(Request):在web應用中,爲每個請求創建一個bean實例

單例是默認的作用域,對於不適合單例的要選擇其它作用域,需要使用@Scope註解,它可以與@Component或@Bean一起使用在類或者方法上。例如將一個類聲明爲原型bean:

這裏使用 ConfigurableBeanFactory類的SCOPE_PROTOTYPE常量設置了原型作用域,當然,也可以直接在註解裏面寫成:@Scope("prototype"),但是字面量畢竟容易出錯,使用常量更加安全。同樣,在配置類中顯式配置bean也可以使用:

同樣,在xml配置中,可以使用bean元素的scope屬性來配置:

再來看一個購物車的例子。在電商網站上,每個用戶都有一個自己的購物車,這個購物車的bean不能設置爲單例作用域,否則全網站只有一個購物車肯定不合適,也不能設置爲原型作用域,否則每次使用都會創建一個新的購物車,以前加進去的都沒了,同樣的道理,配置成請求作用域也會存在這個問題。所以,最合適的是會話作用域,因爲它與用戶的關聯性最大,要指定會話作用域,我們可以使用@Scope註解,使用方式是相同的:

這裏的WebApplicationContext類需要引入spring-web依賴。這樣就會在web應用中爲每個會話創建一個購物車實例。也就是對於同一個會話,購物車相當於單例。

@Scope註解還有一個屬性 proxyMode,它的值在購物車中被設置成了 ScopedProxyMode.INTERFACES,這個屬性解決了將會話或者請求作用域的bean注入到單例的bean中所遇到的問題,在討論proxyMode之前,先來看一下它應用的場景,假設我們要講購物車的bean注入到電商網站的bean中:

因爲Store是一個單例的bean,會在Spring應用上下文加載時創建,當它創建時,Spring會試圖將ShoppingCart注入到Store中,但是ShoppingCart是會話級別的bean,因此此時並不存在,直到第一個用戶登錄有了會話後纔有ShoppingCart實例。

另外,系統中將會有多個ShoppingCart實例,因爲每個用戶一個,所以並不應該注入某個固定的ShoppingCart實例到Store中,最好Store處理購物車業務時,恰好是當前用戶對應的那一個。

因此Spring並不會將實際的ShoppingCart實例注入到Store中,Spring會注入一個ShoppingCart的bean的代理,

如圖,這個代理會暴露與ShoppingCart相同的方法,所以Store會認爲它就是一個購物車,但是當Store調用ShoppingCart的方法時,代理會對其進行懶解析並將調用委託給會話作用域內真正的 ShoppingCart 的bean。

現在我們可以來討論一下proxyMode屬性,ShoppingCart的proxyMode屬性被設置成了 ScopedProxyMode.INTERFACES,這個屬性值表明,這個代理要實現ShoppingCart接口,並將調用委託給實際實現這個接口的bean。

如果ShoppingCart是接口而不是具體類,這樣是可以的,也是最理想的模式。但是如果ShoppingCart是一個具體類的話,Spring就沒有辦法創建基於接口的代理了,此時,必須使用 CGLib 來生成基於類的代理,所以如果bean類型是具體類的話,我們必須要將proxyMode的值設置爲 ScopedProxyMode.TARGET_CLASS,以此表示要爲類創建代理,所以代碼應該改爲:

上面我們主要討論的會話作用域,不過請求作用域也會面臨相同的問題,因此請求作用域的bean應該也以作用域代理的方式進行注入。

上面討論的是Java配置類和自動裝配的方式,在xml中配置會話作用域和請求作用域的bean,需要使用spring aop命名空間的一個新元素(需要聲明aop命名空間,後面討論aop會具體學習):

<aop:scoped-proxy/>是與@Scope註解的proxyMode屬性功能相同的Spring xml配置元素。它會告訴Spring bean創建一個作用域代理,默認情況下,它會使用 CGLib創建目標類的代理,如果想要求生成基於接口的代理,需要設置 proxy-target-class="false" ,

5. 運行時值注入


Spring 的ioc中,依賴注入就是將一個bean的引用注入到另一個bean的屬性或者方法參數中。本質上說就是將一個對象與另一個對象進行關聯。不過前面的例子中,也有不少是直接在創建bean的時候賦值的,比如:

這種是採用硬編碼的方式,直接給bean賦的初始值。有時候是可以的,但是很多時候,我們更希望避免硬編碼,讓這些屬性或參數的值在運行時再確定,Spring提供了兩種方式實現了運行時值注入的功能:

  • 屬性佔位符(Property placeholder)
  • Spring表達式語言(SpEL)

這兩種的用法有點類似,不過目的和行爲所有差別,其中屬性佔位符方式較爲簡單,先來看一下這種方法。

Spring中,處理外部值最簡單的方式就是聲明屬性源,並通過Spring的Environment來檢索屬性。舉個栗子,首先創建一個屬性文件:

然後看配置:

代碼中,引入配置文件app.properties 使用了註解 @PropertySource, 這個屬性文件的內容會加載到Spring的Environment中,然後就可以從這裏檢索出屬性。在配置方法中可以看到,屬性的獲取時通過 getProperty 方法實現的。下面來具體瞭解一下 Environment。

調用Environment方法的時候發現,getProperty並不是獲取屬性的唯一方法:

getProperty方法有四個重載 :

  • String getProperty(String key)
  • String getProperty(String key, String defaultValue)
  • T getProperty(String key, Class<T> type)
  • T getProperty(String key, Class<T> type, T defaultValue)

前兩種方法都是返回String類型,第一種方法我們已經使用過了,第二種方法多了一個參數,默認值。也就是在獲取不到指定屬性的值的時候,直接返回一個默認值。剩下的兩種getProperty方法與前面兩種非常類似,但是它可以對返回類型進行設置,不一定是String類型。例如我們想要的值只是一個數量,這樣就能直接獲取對應的類型,而不必獲取String,然後在進行類型轉換:

Environment還提供了幾個與屬性相關的方法,如果你在使用 getProperty方法的時候,沒有指定默認值,並且這個屬性也沒有定義的話,獲取到的值是null,如果你希望這個屬性必須要定義,那麼可以使用getRequiredProperty方法:

在這裏,如果屬性 "app.user.age" 沒有定義,會拋出 IllegalStateException 異常。如果想檢查一下屬性是否存在可以使用 containsProperty 方法:

Environment還提供了一些方法來檢查哪些profile處於激活狀態:

  • String[] getActiveProfiles() 返回激活profile名稱的數組
  • String[] getDefaultProfiles() 返回默認profile名稱的數組
  • boolean acceptsProfiles(String... profiles) 如果Environment支持給定profile的話,就返回true

在前面的討論條件化創建bean的例子中,我們提到了 環境的的註解 @Profile 的底層實現使用的就是條件化創建,條件邏輯類是 ProfileCondition :

可以看到這個類中就使用了 acceptsProfiles 方法。Environment的方法不經常使用,但是一定要了解。直接從Environment中獲取屬性是非常方便的,尤其是在使用Java配置類的時候,不過Spring也提供了通過佔位符裝配屬性的方法,佔位符的值來源於一個屬性源。在Spring裝配中,佔位符的形式爲 "${...}" 包裝的屬性名稱。

在Java配置類中,可以使用@Value註解來配置屬性,例如:

或者直接放在參數中:

這樣配置bean的時候,可以直接使用:

再來看xml配置的例子,首先引入配置文件,需要使用元素標籤 context:property-placeholder ,然後引用屬性值:

Spring 表達式語言(Spring Expression Language, SpEL)提供了另外一種更爲通用的在運行時注入值。它能夠以一種強大和簡潔的方式將值裝配到bean屬性和方法參數中,在這個過程中表達式會在運行時計算得到值。使用SpEL可以實現非常不錯的裝配效果。SpEL擁有很多特性,包括:

  • 使用bean的id來引用bean
  • 調用方法和訪問對象的屬性
  • 對值進行算數、關係和邏輯運算
  • 正則表達式匹配
  • 集合操作

而且SpEL還能夠應用在依賴注入以外的地方(例如,Spring Security)。首先來看SpEL的幾個例子,以及如何將其注入到bean中。

需要了解的第一件事就是SpEL表達式要放到 "#{...}" 之中,這與屬性佔位符有點類似,屬性佔位符需要放到 "${...}" 之中。下面來看一個最簡單的SpEL表達式:#{1} ,大括號內部的就是實際的SpEL表達式體了。這裏是一個數字常量 1 ,很明顯,這個表達式的結果就是 1 。當然實際的表達式肯定更復雜一些。再來看一個例子: #{T(System).currentTimeMillis()} ,它的最終計算結果是獲取那一刻的時間毫秒數。T()表達式會將java.lang.System視爲Java中對應的類型,因此,可以調用它內部的靜態方法 currentTimeMillis() ,舉個栗子:

SpEL表達式也可以引用其它的bean或其它bean的屬性,例如:#{role.titles} ,這個表達式會計算得到id爲role的bean的titles屬性,代碼示例如下(role0202方法):

我們還可以通過systemProperties對象引用系統屬性,比如獲取操作系統的名字:

同時,我們可以自定義自己的配置文件的屬性對象,然後以同樣的方式引用:

上面是幾個在配置類中的基本使用的例子,同樣,在xml配置文件中使用方式類似,如獲取系統屬性:

獲取用戶自定義屬性:

前面是幾個簡單的樣例,下面來看一下SpEL所支持的基礎表達式。前面已經看到一個表示常量的例子:#{1} ,同樣,表示浮點值就是: #{3.14} ,表示科學計數法就是 : #{9.87E4} ,表示乘以10的四次方,就是 98700 ,也可以直接表示String類型的值: #{'Jack'} ,也可以表示boolean類型的值: #{true} 。當然單獨包含一個字母量的值其實意義不大,但是由多個字面量、常量和變量組成的表達式是非常有用的。

前面的例子中,有對bean屬性的引用 : #{role.titles} ,不僅是屬性,還可以調用bean的方法: #{role.say()} ,還可以連續調用: #{role.say().toString()} ,不過如果 say() 方法的返回值爲null就會報異常,爲了避免這個問題,我們可以使用安全運算符: #{role.say()?.toString()} ,就是在無法確定的 say() 方法後面加個?,這樣如果 say() 方法返回null ,就不會繼續調用後面的 toString() 方法,表達式的結果直接返回 null 。

前面的例子中,有獲取時間戳的例子: #{T(System).currentTimeMillis()} ,在SpEL表達式中,可以直接使用類型。如果要訪問類的靜態方法和常量的話,需要依賴 T() 這個關鍵的運算符,例如,使用Math類就需要像下面這樣寫: #{T(java.lang.Math)} ,這裏的 T() 運算符的結果是一個class對象,這個運算符的真正價值就在於,它能訪問類的靜態方法和常量。例如,裝配的時候需要常量 PI ,可以這樣寫: #{T(java.lang.Math).PI} ,獲取一個隨機數,就需要調用方法,可以這樣寫,#{T(java.lang.Math).random()} 。

SpEL提供了很多運算符,這些運算符可以運用在表達式上:

舉個例子: #{2T(java.lang.Math).PIcircle.radius} ,這個例子展示了將簡單的表達式組合爲複雜的表達式,它的計算結果顯而易見,就是圓的周長。 #{T(java.lang.Math).PI*circle.radius^2} 就是圓的面積,"^" 是用於乘方計算的運算符。

當使用String類型的值時 “+” 符號就表示連接操作: #{'hello ' + 'world'} ,與Java代碼中是一樣的,SpEL還提供了比較運算符,有符號和文本兩種形式:

  • 1.#{user.age == 18} 與 #{user.age eq 18} 等價
  • 2.#{user.age > 18} 與 #{user.age gt 18} 等價
  • 3.#{user.age < 18} 與 #{user.age lt 18} 等價
  • 4.#{user.age >= 18} 與 #{user.age ge 18} 等價
  • 5.#{user.age <= 18} 與 #{user.age le 18} 等價

計算結果是boolean類型 true或者false。SpEL還提供了三元運算符,與Java代碼中的很類似,比如判斷年齡,大於18就是成人:#{user.age >= 18 ? 'man' : 'child'} 。三元運算符常見的一種場景就是檢查null值,如果是null就用一個默認的值來代替null值,下面會判斷name是否爲null,如果是null就返回 "Jack" :

  • .#{user.name ?: 'Jack'}

當處理文本時,有時候檢查文本是否匹配某種規則是非常有用的,比如正則表達式。SpEL通過matches運算符支持表達式中的模式匹配。matches運算符對String類型的文本(作爲左邊參數)進行匹配正則表達式(作爲右邊參數)。如果匹配就返回true,反之返回false。假設我們想檢查一個問題是否是郵件地址:

  • .#{user.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.com'}

除了正則表達式,SpEL最令人驚歎的技巧是操作集合和數組。比如:#{role.users[2].title} 表示引用一個元素。比如: #{role.users[T(java.lang.Math).random()*role.users.size()].title} 表示隨機獲取一個人的title。

"[]"運算符用來從集合或者數組中按照索引獲取元素。不僅如此,它還可以從字符串中獲取一個字符,比如獲取第五個:#{'hello'[4]} ,結果就是 "e" .

SpEL還提供了查詢運算符(.?[]),它會用來對集合進行過濾,得到集合的一個子集。假設,要獲取角色中,所有年齡大於18歲的用戶:#{role.users .?[age > 18]} ,可以看到查詢運算符的方括號中有另一個表達式,這個表達式結果爲true,這個元素就會放到結果列表中。

SpEL還提供了投影運算符(.![]),它從集合的每個成員中選擇特定的屬性放到另外一個集合中。假設我們要獲取所有用戶的名字:#{role.users .![name]} ,實際上,投影的操作可以與其它任意的運算符一起使用,比如將查詢運算符與投影一起使用:#{role.users .?[age>18] .![name]} ,表示所有成人的名字。

SpEL還有很多例子,不過要注意,寫SpEL的時候,不要過於複雜,否則維護和測試起來都很麻煩。簡單直接就可以。

6. 總結


這篇內容是上一篇的延續,學習了很多spring ioc的高級技巧,完善了spring ioc的整個知識結構 ,爲後面的內容學習 spring aop做好準備。

代碼地址:https://gitee.com/blueses/spring-framework-demo
11-12:profile與環境配置
13:條件化的bean
14-17:自動裝配的歧義性
18:bean的作用域
19-20:運行時值注入

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章