這邊紀錄關於透過 bean configuration file設定autowiring 之設定方式
public interface LogWriter { public void write(String text); }
public class FileWriter implements LogWriter { public void write(String text) { System.out.println("Write to file: " + text); } }
public class ConsoleWriter implements LogWriter { public void write(String text) { System.out.println("Write to console " + text); } }
public class Logger { private ConsoleWriter consoleWriter; private FileWriter fileWriter; public Logger() {} public Logger(ConsoleWriter consoleWriter, FileWriter fileWriter) { this.consoleWriter = consoleWriter; this.fileWriter = fileWriter; } public void setConsoleWriter(ConsoleWriter writer) { this.consoleWriter = writer; } public void setFileWriter(FileWriter fileWriter) { this.fileWriter = fileWriter; } public void writeFile(String text) { fileWriter.write(text); } public void writeConsole(String text) { consoleWriter.write(text); } }
public class App { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("/org/springframework/example/beans/beans.xml"); Logger logger = (Logger)context.getBean("logger"); logger.writeConsole("Hello there"); logger.writeFile("Hi again"); ((ClassPathXmlApplicationContext)context).close(); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="fileWriter" class="org.springframework.example.FileWriter"> </bean> <bean id="consoleWriter" class="org.springframework.example.ConsoleWriter"> </bean> <bean id="logger" class="org.springframework.example.Logger"></bean> </beans>
這邊定義了一個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。
private LogWriter logWriter; public void setLogWriter(LogWriter logWriter) { this.logWriter = logWriter; }
如上面情況就會拋出錯誤說明找不到適合的bean,因為有兩個符合條件的bean存在。
2. Autowiring by Name
byName的方式與byType類似,byName就是以bean 的name 或 id 來做autowiring。<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.springframework.example.FileWriter" id="logTest" name="fileWriter"> </bean> <bean id="consoleWriter" class="org.springframework.example.ConsoleWriter"> </bean> <bean id="logger" class="org.springframework.example.Logger" autowire="byName"> </bean> </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 |
改為這樣後,執行結果與之前相同。
同樣需要注意的是,如果 bean.xml內某個注入類別的 bean有一個以上時,可能會造成Spring判斷上的不確定,遇到這情況,Spring就會改以 id或 name作為判斷依據,由此來選擇注入。
這邊調整beans.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="consoleWriter" class="org.springframework.example.ConsoleWriter"> </bean> <bean class="org.springframework.example.FileWriter" id="logTest" name="fileWriter"> </bean> <bean id="logWriter" class="org.springframework.example.ConsoleWriter"> </bean> <bean id="logger" class="org.springframework.example.Logger" autowire="constructor"> </bean> </beans>
在runtime時,Logger同樣雖然可以正常執行,但只要我們修改一個地方,程式就會沒辦法正常注入,稍微調整constructor:
public Logger(ConsoleWriter consoleWriter1, FileWriter fileWriter) { this.consoleWriter = consoleWriter1; this.fileWriter = fileWriter; }
調整了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
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byType"> <bean id="consoleWriter" class="org.springframework.example.ConsoleWriter"> </bean> <bean class="org.springframework.example.FileWriter" id="logTest" > </bean> <bean id="fileWriter" class="org.springframework.example.FileWriter"> </bean> <bean id="logger" class="org.springframework.example.Logger"> </bean> </beans>
這邊設定了 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:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byType"> <bean id="consoleWriter" class="org.springframework.example.ConsoleWriter"> </bean> <bean class="org.springframework.example.FileWriter" id="logTest"> </bean> <bean id="fileWriter" class="org.springframework.example.FileWriter"> </bean> <bean id="logger" class="org.springframework.example.Logger"> </bean> </beans>
這時候程式應該是會出錯的,這時候我們在 logTest這個bean上面設定 autowire-candidate為 false,程式就可以正常執行了,因為logTest這個 bean 被排除在作為注入的bean 來源,所以就不會有 ambiguous的問題。
beans.xml |
bean 上的 primary:
除了針對各個bean 設定autowire-candidate 屬性外,還有一個primary 屬性,primary的目的是在Spring遇到ambiguous 情況時,可以選擇優先注入的 bean 來源。同樣的設定檔(沒有設定autowire-candidates):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byType"> <bean id="consoleWriter" class="org.springframework.example.ConsoleWriter"> </bean> <bean class="org.springframework.example.FileWriter" id="logTest"> </bean> <bean id="fileWriter" class="org.springframework.example.FileWriter"> </bean> <bean id="logger" class="org.springframework.example.Logger"> </bean> </beans>這情況下 fileWriter與 logTest 都可以作為 FileWriter 類別注入的來源,所以我們可以在其中一個上面設定 primary為 true,Spring就會知道有一個以上可以注入來源,要選擇primary的bean 作為注入來源。
beans.xml |
透過 bean configuration file 設定 bean屬性的部分大致上就到這邊,當然還有相當多可以探討的屬性可以使用,這邊只記錄一部份。
後面會接著記錄透過 Annotation方式來設定 Autowire,改透過程式來設定注入方式,也是實際在開發時比較常使用的設定方式。
這邊都是上這門 udemy課程後所做的筆記,如果有興趣,可以參考這門課程。
沒有留言:
張貼留言