Java Spring Framework 筆記 - Autowiring(1)


這邊紀錄關於透過 bean configuration file設定autowiring 之設定方式


  1. public interface LogWriter {
  2. public void write(String text);
  3. }
  1. public class FileWriter implements LogWriter {
  2.  
  3. public void write(String text) {
  4. System.out.println("Write to file: " + text);
  5. }
  6.  
  7. }
  1. public class ConsoleWriter implements LogWriter {
  2.  
  3. public void write(String text) {
  4. System.out.println("Write to console " + text);
  5. }
  6.  
  7. }
  1. public class Logger {
  2.  
  3. private ConsoleWriter consoleWriter;
  4. private FileWriter fileWriter;
  5. public Logger() {}
  6. public Logger(ConsoleWriter consoleWriter, FileWriter fileWriter) {
  7. this.consoleWriter = consoleWriter;
  8. this.fileWriter = fileWriter;
  9. }
  10. public void setConsoleWriter(ConsoleWriter writer) {
  11. this.consoleWriter = writer;
  12. }
  13.  
  14. public void setFileWriter(FileWriter fileWriter) {
  15. this.fileWriter = fileWriter;
  16. }
  17.  
  18. public void writeFile(String text) {
  19. fileWriter.write(text);
  20. }
  21. public void writeConsole(String text) {
  22. consoleWriter.write(text);
  23. }
  24.  
  25. }
  1. public class App {
  2. public static void main(String[] args) {
  3. ApplicationContext context = new ClassPathXmlApplicationContext("/org/springframework/example/beans/beans.xml");
  4. Logger logger = (Logger)context.getBean("logger");
  5. logger.writeConsole("Hello there");
  6. logger.writeFile("Hi again");
  7. ((ClassPathXmlApplicationContext)context).close();
  8. }
  9. }
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5.  
  6.  
  7. <bean id="fileWriter"
  8. class="org.springframework.example.FileWriter">
  9. </bean>
  10. <bean id="consoleWriter"
  11. class="org.springframework.example.ConsoleWriter">
  12. </bean>
  13. <bean id="logger" class="org.springframework.example.Logger"></bean>
  14. </beans>
  15.  


這邊定義了一個interface,並設計了兩種Log模式,這邊的目的在於將不同的Writer注入到Logger類別內,這邊透過設定xml內的 autowiring來做到

1. Autowiring by Type

Logger類別內,分別需要一個 ConsoleWriter與FileWriter,而在 beans設定檔內,如果沒有其他ConsoleWriter與FileWriter類別的 bean,就可以使用 Autowiring by type方式來做 autowiring;如果有其他重複類別,則會拋出錯誤,因為這情況Spring沒辦法決定要注入哪一個類別。




設定好之後,在執行時,Spring看到 byType設定,就會依照bean內提供的 set方法,依照類別找尋定義檔內可提供注入的 bean,並自動注入到Logger類別內,透過這方式,可以只要加上autowire設定就自動注入。


這個方式的缺點是如果Logger需要注入一個 LogWriter類別時,就會拋出錯誤,因為ConsoleWriter與FileWriter皆繼承自LogWriter,遇到這一類情況就不適合使用byType方式作autowire。


  1. private LogWriter logWriter;
  2. public void setLogWriter(LogWriter logWriter) {
  3. this.logWriter = logWriter;
  4. }


如上面情況就會拋出錯誤說明找不到適合的bean,因為有兩個符合條件的bean存在。


2. Autowiring by Name

byName的方式與byType類似,byName就是以bean 的name 或 id 來做autowiring。

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  4.  
  5.  
  6. <bean class="org.springframework.example.FileWriter" id="logTest"
  7. name="fileWriter">
  8. </bean>
  9.  
  10. <bean id="consoleWriter"
  11. class="org.springframework.example.ConsoleWriter">
  12. </bean>
  13. <bean id="logger" class="org.springframework.example.Logger"
  14. autowire="byName">
  15. </bean>
  16. </beans>


這邊將logger autowire方式改為byName,另外為了測試,將原本fileWriter bean id改為logTest,並於name屬性設為 fileWriter。

程式執行後,logger 有 setConsoleWriter與 setFileWriter,所以Spring會找尋 id 或 name 為 consoleWriter或 fileWriter的 bean注入到 logger內。


3. Autowiring by Constructor

與 constructor 注入很類似,在類別的 constructor內定義了要注入的內容,接著在 beans.xml上設定 autowire方式為 constructor,在執行時,Spring 會依據類別內最多符合注入條件的 constructor選來作為注入,注入方式應該是 byType再byName(純個人推測)

beans.xml
這邊延續前一個設定,僅將 Logger bean 的 autowire改為constructor,在程式執行時,會由Spring自動判別有哪些可用的bean可作為注入。

改為這樣後,執行結果與之前相同。

同樣需要注意的是,如果 bean.xml內某個注入類別的 bean有一個以上時,可能會造成Spring判斷上的不確定,遇到這情況,Spring就會改以 id或 name作為判斷依據,由此來選擇注入。

這邊調整beans.xml定義檔:


  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5.  
  6.  
  7. <bean id="consoleWriter"
  8. class="org.springframework.example.ConsoleWriter">
  9. </bean>
  10. <bean class="org.springframework.example.FileWriter" id="logTest"
  11. name="fileWriter">
  12. </bean>
  13. <bean id="logWriter"
  14. class="org.springframework.example.ConsoleWriter">
  15. </bean>
  16. <bean id="logger" class="org.springframework.example.Logger"
  17. autowire="constructor">
  18. </bean>
  19. </beans>
  20.  

在runtime時,Logger同樣雖然可以正常執行,但只要我們修改一個地方,程式就會沒辦法正常注入,稍微調整constructor:

  1. public Logger(ConsoleWriter consoleWriter1, FileWriter fileWriter) {
  2. this.consoleWriter = consoleWriter1;
  3. this.fileWriter = fileWriter;
  4. }

調整了constructor參數名稱,將原本consoleWriter改為consoleWriter1後執行程式,就會拋出 NullPointerException。

Spring透過byType找尋可注入的bean時,在發現一個以上可供注入的bean的情況下,會由原本 byType方式改為byName繼續查詢,而 beans.xml內可提供注入的ConsoleWriter類別有兩個,這兩個bean的 id或name都不符合byName的注入條件(沒有id或name為 consoleWriter1的bean),所以沒有如同預期的將 bean注入到Logger內。

這算是方便背後需要注意的,雖然提發現有多供了一些方式可以省掉一些設定步驟,但一個不小心還是可能採到地雷。

4. Default Autowire & Default autowire candidates

前面遇到一些Spring 無法自動注入的情況,原因是因為Spring在注入時,有多個bean可以注入到一個變數的情況下,Spring無法選擇要注入何者,這些問題可以透過一些方式排除,其中一種是可以透過設定default autowire與 default autowire candidates解決。

default-autowire:

在beans.xml root beans的地方,可以設定 default-autowire,同樣可設定為no, default, byName, byType, constructor,設定為其中一項後,在這個Spring  bean定義檔內的所有bean在沒有設定 autowire方式的情況下會以 root bean的 default autowire方式來做 autowire

default-autowire-candidate:

在root bean的 default-autowire-candidates屬性,可以視為在這個beans.xml定義檔中,所有加在 default-autowire-candidates內的bean才會由Spring 拿來做注入,若不在candidates名單內,Spring不會用來做注入。

可以在 dfault-autowire-candidates上設定要注入的 bean id,也可以透過 * 來指定符合特定名稱的 bean。

這邊稍微調整 beans.xml設定檔:

beans.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
  5. default-autowire="byType">
  6.  
  7.  
  8. <bean id="consoleWriter"
  9. class="org.springframework.example.ConsoleWriter">
  10. </bean>
  11. <bean class="org.springframework.example.FileWriter" id="logTest"
  12. >
  13. </bean>
  14. <bean id="fileWriter"
  15. class="org.springframework.example.FileWriter">
  16. </bean>
  17. <bean id="logger" class="org.springframework.example.Logger">
  18. </bean>
  19. </beans>
  20.  

這邊設定了 default-autowire方式為 byType,這時直接執行程式Spring會拋出Exception,因此可以試著在 default-autowire-candidates屬性上加入 consoleWriter與 fileWriter:

beans.xml

beans.xml

執行結果

需要留意的是 default-autowire-candidates欄位不可已有空格,
如果不小心打為consoleWriter, fileWriter,那Spring會找不到 fileWriter來做注入。
執行程式後,Spring會先透過 byType方式來做注入,而 FileWriter類別因為有兩個可注入的選擇,Spring會依據 default-autowire-candidates 內設定的資訊,來找尋是否有符合的 id(不包含name),而 id為 fileWriter的bean 符合條件,Spring選擇作為注入的來源。


同樣的,我們可以再調整上面的設定,改為 *Writer,表示所有以id 以Writer結尾的都會視為candidates:

beans.xml
beans.xml

執行後可以得到同樣的結果:

執行結果

bean 上的 autowire-candidate:

各個bean上有一個 autowire-candidate屬性,可以針對單一個 bean 來設定是否要作為candidates bean 使用,這屬性可以設定為 true或 false,true為可作為autowire注入來源,false則不做為 autowire 注入來源。

這邊再一次調整 beans.xml,將default-autowire-candidates屬性拿掉,只設定 default-autowire為 byType:


  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
  5. default-autowire="byType">
  6.  
  7.  
  8. <bean id="consoleWriter"
  9. class="org.springframework.example.ConsoleWriter">
  10. </bean>
  11. <bean class="org.springframework.example.FileWriter" id="logTest">
  12. </bean>
  13. <bean id="fileWriter"
  14. class="org.springframework.example.FileWriter">
  15. </bean>
  16. <bean id="logger" class="org.springframework.example.Logger">
  17. </bean>
  18. </beans>
  19.  

這時候程式應該是會出錯的,這時候我們在 logTest這個bean上面設定 autowire-candidate為 false,程式就可以正常執行了,因為logTest這個 bean 被排除在作為注入的bean 來源,所以就不會有 ambiguous的問題。

beans.xml

bean 上的 primary:

除了針對各個bean 設定autowire-candidate 屬性外,還有一個primary 屬性,primary的目的是在Spring遇到ambiguous 情況時,可以選擇優先注入的 bean 來源。

同樣的設定檔(沒有設定autowire-candidates):

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
  5. default-autowire="byType">
  6.  
  7.  
  8. <bean id="consoleWriter"
  9. class="org.springframework.example.ConsoleWriter">
  10. </bean>
  11. <bean class="org.springframework.example.FileWriter" id="logTest">
  12. </bean>
  13. <bean id="fileWriter"
  14. class="org.springframework.example.FileWriter">
  15. </bean>
  16. <bean id="logger" class="org.springframework.example.Logger">
  17. </bean>
  18. </beans>
  19.  
這情況下 fileWriter與 logTest 都可以作為 FileWriter 類別注入的來源,所以我們可以在其中一個上面設定 primary為 true,Spring就會知道有一個以上可以注入來源,要選擇primary的bean 作為注入來源。

beans.xml

透過 bean configuration file 設定 bean屬性的部分大致上就到這邊,當然還有相當多可以探討的屬性可以使用,這邊只記錄一部份。

後面會接著記錄透過 Annotation方式來設定 Autowire,改透過程式來設定注入方式,也是實際在開發時比較常使用的設定方式。

這邊都是上這門 udemy課程後所做的筆記,如果有興趣,可以參考這門課程。

沒有留言:

張貼留言

Java Spring Framework 筆記 - Autowiring (2)

這篇記錄透過 Annotation來做到 Spring的 autowiring。