Java Spring Framework 筆記 - Autowiring(1)


這邊紀錄關於透過 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
這邊延續前一個設定,僅將 Logger bean 的 autowire改為constructor,在程式執行時,會由Spring自動判別有哪些可用的bean可作為注入。

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

同樣需要注意的是,如果 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課程後所做的筆記,如果有興趣,可以參考這門課程。

沒有留言:

張貼留言

Java Spring Framework 筆記 - Autowiring (2)

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