2011-02-15

Spring IoC Container 3.0.5 之一

  • IoC(全名是 Inversion of Control,字面意思是反轉控制,也有另一個名字 Dependency Injection):a 物件如果要用到 b 物件,以前的作法是,在 a 物件中「設法」去取得 b 物件,如此會讓取得 b 物件的邏輯散佈整個系統,IoC 的作法是,透過設定的方式,讓 container 將 b 物件塞到 a 物件裡,也就是 inversion 和 inject 的意思。
  • Spring Ioc Container 最上層的 interface 是 BeanFactory,主要負責產生、設定、組裝的工作,而 ApplicationContext 繼承 BeanFactory ,加上 AOP 等商業層的功能。
  • Spring Ioc Container 有三種設定方式:XML、Java Annotation(Spring 2.5)、Java Code(Spring 3.0)。
    • Java Annotation 已經取代 XML 成為主要的設定方式。
      • 為什麼要用 Java Annotation 取代 XML:因為將程式與設定值放在同一個檔案裡,遠比分散在兩個檔案裡好維護;在之前,Hibernate 已經導入 Annotaion了。
    • XML 是透過 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext 來啟動 Ioc Container。
    • Java Annotation 還是需要少少的 XML 來告訴 Ioc Container 到哪讀取 Java Annotation。
    • 而在 webapp 中,只要在 web.xml 做些設定就可以啟動 Ioc Container。
  • 簡單的 Spring Ioc Container 說明
    • 輸入:Java code 和 設定(以XML、Java Annotation 或 Java Code 方式陳述初始化、設定、組裝的資訊)
    • Spring Ioc Container 加工中...
    • 輸出:一個ready-to-use的系統
  • XML 設定
    <beans>
    <import resource="..."/>
    <bean id="..." class="...">...</bean>
    </bean>
    • import 可以讀入另一個 Spring XML 檔,resource 建議使用相對路徑,相對於目前這個 XML 檔案位置,若在 resource 中使用以 / 開頭的絕對路徑,該 / 會被略過,一概視為相對路徑。
    • import 的 resource 不建議使用該 XML 檔案位置以上(或以外的)路徑檔案,如../...,因為會因環境改變導致讀檔錯誤。
    • import 的 resource 可以使用絕對路徑,如 classpath: /com/... 或者 file: c:/...,但不建議寫死絕對路徑,可以用 ${...} 讀取系統參數或 properties 的方式設定絕對路徑。
    • Spring 在啟動時會將這些 XML 設定讀取寫到 BeanDefinition 裡。
  • 使用 container
    ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"service.xml", "dao.xml"});
    UserService userService = ctx.getBean("userService", UserService.class);
    • 一般像是使用 webapp,並不需要直接用到 ApplicationContext 之類的 Spring API,也就是在程式面,系統並不會與 Spring 有相依關係。
    • getBean(...) 已經支援泛型。
  • Bean 的 id
    • 目前還不知道 id 與 name 的差異,只知道 XML 會禁止重複的 id,但是允許重複的 name。
    • 一個 bean 可以有多個 id,但是一個 id 不可以給多個 bean 使用,也就是 id 必須是唯一的。
    • 可以利用 name 來取 alias name,在 name 裡可以用逗號、分號或空白區隔多個別名,但是取別名做啥咧?
      <bean id="a" name="b,c,d" class="..."/>
      • 另一個取別名的方法
        <alias name="name" alias="aliasName"/>
    • Bean 可以不要有 id 或 name,Spring 會自動為沒有 id 的 bean 取名字,什麼情況會不需要 id?
      • 沒有其他 bean 會 reference 到這個無名的 bean。
      • Inner Bean
      • 使用 AutoWiring。
  • 初始化 bean
    • Spring 有三種初始化 bean 的方式:透過 constructor、使用 static factory method、使用 instance factory method。
    • 透過 constructor:必須有合適的 constructor。
      <bean id="..." class="..."/>
    • 使用 static factory method:factory method 的參數借用 constructor-arg 傳入,factory class 不必與回傳的 class 一致。
      <bean id="..." class="..." factory-method="methodNameOnly">
      <constructor-arg .../>
      </bean>
    • 使用 instance factory method:不用指定class,factory method 的參數借用 constructor-arg 傳入,factory class 不必與回傳的 class 一致。
      <bean id="..." factory-bean="anotherSpringBeanId" factory-method="methodNameOnly">
      <constructor-arg .../>
      </bean>
  • Dependency Inject(DI)
    • Bean 不用自己去找 reference,container 會自動將 reference 送上門,所以 bean 的 code 變得很乾淨,也方便測試。
    • DI 主要有兩種方式:透過 constructor 或 settor。
    • Constructor Inject
      • 用 ref 時,有 index 用 index,沒有 index 用型別。
      • 用 value 時,因為沒有型別,最好搭配 type 或 index 使用。
      • 從 Spring 3.0 開始,也可以用 name 表示參數的名字。
      • index 是從 0 開始。
    • Constructor Inject 可以和 Settor Inject 混合使用。
      • 使用 Constructor Inject 來設定必要的 reference。
      • 使用 Settor Inject 來設定可有可無的 reference。
      • 不建議全部用 Constructor Inject,這樣會讓 constructor 很笨重。
      • Settor Inject 還有一個可以重新設定的好處。
    • 如果 bean a 用到 bean b,container 會先將 bean b 準備妥當,包括初始化、設定 reference、呼叫 callback 等,才會將 bean b 塞到 bean a 裡,所以當 bean a 得到 bean b 時,bean b 是準備就緒的。
    • ApplicationContext 在啟動時,會將所有 singlton bean 都準備就緒,而 prototype bean 則會等到被呼叫時才會產生,所以 prototype bean 的設定正確與否不會在ApplicationContext 啟動時檢查。
  • 各種 property 給值方法:
    • 使用 value
      <property name="..." value="..."/>
    • 使用 p-namespace
      <beans ... xmlns:p="http://www.springframework.org/schema/p" ... >
      <bean ... p:propertyName="propertyValue" ... />
    • property 的型別為 java.util.Properties
      <property name="...">
      <value>
      aaa=111
      bbb=222
      </value>
      </property>
    • 將 bean a 的 id (字串,不是 reference) 傳給 bean b
      • 沒有任何檢查機制
        <property name="..." value="beanA"/>
      • 由 Spring container 做檢查
        <property name="...">
        <idref bean="beanA"/>
        </property>
      • 由 XML 做檢查,但 bean a 與 bean b 必須在同一個 XML 檔案裡
        <property name="...">
        <idref local="beanA"/>
        </property>
    • 使用 ref 給 reference
      • ref tag有三種指定 reference 方式:
        <ref bean="..."/>
        <ref local="..."/>
        <ref parent="..."/>
      • ref bean 是最常用的方式,可以參考到同一個 container 或者 parent container ,不管是不是在同一個檔案裡,bean 可以是目標 bean 的 id 或者 其中一個 name。
      • ref local 是利用 XML 的驗證機制,local 必須是同一個 XML 檔案裡的 id,不可以是 name。
      • ref parent 必須是 parent container 裡的 id 或 其中一個 name,為什麼不用 bean 要用 parent 呢?目的在於要在 child container 裡使用與 parent bean 同名的 bean。
    • Inner bean
      • 不需要 id、name、scope 等屬性。
      • scope 一律使用 prototype。
        <property name="...">
        <bean .../>
        </property>
    • Collections
      • 使用 <list/>、<set/>、<map/>、<props/> 對應 Java 的 collection 物件 List、Set、Map、Properties。
      • Spring 已經完全支援泛型,所以可以使用 List<E>、Set<E>、Map<K,V>,Spring 會自動驗證型別並轉換。
    • null 與空字串
      • 設定為 null 的方式:
        <property name="..."><null/></property>
      • 設定為空字串的方式:
        <property name="..." value=""/>
    • Nested Path
      • 可以在 property name 使用巢狀路徑,除了最後標的以外的路徑物件必須是 not null 。
        <property name="userDTO.name" value="egg"/>
  • depends-on
    • 用於當 a 物件初始化時,b 物件必須先初始化且兩物件沒有相依的情況,因為如果有相依性,只要 a 物件參考到 b 物件,那 b 物件就會先於 a 物件初始化。
      <bean id="a" class="..." depends-on="b"/>
      <bean id="b" class="..." />
    • 如果 a 物件必須依賴多個其他物件,可以在 depends-on 裡使用逗號、空白或分號區隔多個物件。
    • depends-on 除了定義初始化順序,在 singleton 的狀況下,也決定了 destroy 的順序,a 物件先於 b 物件被系統 destroy。
  • Lazy singleton beans
    • 當 Spring 啟動時會初始化所有的 singleton beans,這樣當 xml 設定有錯時,可以立刻發現。
    • 如果確定 xml 設定沒問題,且不想要在啟動時立即初始化特定的 singleton bean,可以使用 lazy-init 屬性,延遲初始化到第一次呼叫該 bean 時。
      <bean id="..." class="..." lazy-init="true"/>
    • 但是如果該 lazy singleton bean 被另一個 non-lazy singleton bean 參照,那麼 Spring 在啟動時初始化該 non-lazy singleton bean 時,就得初始化該 lazy singleton bean。
    • 可以使用 default-lazy-init 屬性來設定整個 container 的 lazy 效果。
      <beans default-lazy-init="true">...</bean>
  • Autowiring Collaborators
    • 不用指定 ref 屬性,Spring 自動替你做這件事,適用於開發時期的小型系統,大型系統因為 bean 太多容易出錯,正式環境應該使用靜態設定以求穩定性與效能。
    • autowire 屬性有五種設定值:
      • no:預設值,需手動設定 ref 屬性。
      • byName:以 property name 對應 bean id。
      • byType:以 property type 對應 bean class,只能有一個 bean 符合,如果超過一個則會出錯,如果沒有則不設定。
      • constructor:以 byType 方式使用於 constructor 參數,如果找不到對應的 bean 則出錯。
    • byType 與 constructor 可以用於 array 或 typed-collection,Spring 將所有符合的型別都塞進去;也可以用於 typed-map,將符合 value type 的 bean 以其 bean id 為 key 塞到 map 裡。
    • 明確的設定 property 與 constructor-arg 會覆蓋 autowire。
    • 無法使用於 primitive、String 與 Class 等簡單型別,以及其 array 形式。
    • XML 設定檔變得抽象,難以理解。
    • 處理 byType 可能因為多個 bean class 符合而出錯的情況:
      • 在內定的 bean 上設定 primary=true。
      • 或者在不想參加自動配對的 bean 設定 autowire-candidate=false 也可以。
      • 也可以在 beans 上設定 default-autowire-candidates 要參加自動配對的 bean id 的 pattern,如 *Impl,也可以用逗號指定多種 pattern。
      • autowire-candidate 的優先權高於 default-autowire-candidates。
      • 使用上述方式表達不想參加自動配對的 bean 自己可以作為 autowire 的頭。
  • Method Injection
    • 當呼叫 singleton a 物件的 a1 method 時,該 method 需要一個全新的 non-singleton(prototype) b 物件,如果是透過一般 inject 的方式,只會在系統啟動時,塞進一個 b 物件,沒辦法每次呼叫 a 物件的 a1 method 都得到一個新的 b 物件。
    • 解決方式有二,一是 a 物件實做 ApplicationContextAware,然後在 a1 method 中呼叫 getBean() 取得新的 b 物件,但是這種方式就違反 Spring 隱形的精神,另一個方法就是 method injection。
    • Method injection 使用 CGLIB 進行 bytecode generation,複寫 a1 method 並回傳指定的 prototype bean。
    • a1 method 可以是 abstract,但不可是 final,連帶 a class 也不可以是 final 的。
    • a 物件不可以被序列化,why?
    • a1 method 的格式如下:
      <public|protected> [abstract] <return-type> methodName(no-arguments)
    • XML 設定如下:
      <bean ...>
      <lookup-method name="createB" bean="bId"/>
      </bean>
    • b 物件必須是 prototype,才會每次都得到一個新物件,如果是 singleton,那每次都會得到同一個物件。

沒有留言:

張貼留言