Java Spring Framework 筆記 - Autowiring (2)

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





先上程式碼:

pom.xml:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  2. <modelVersion>4.0.0</modelVersion>
  3. <groupId>org.springframework.example</groupId>
  4. <artifactId>spring-annotation-autowiring</artifactId>
  5. <version>0.0.1-SNAPSHOT</version>
  6. <dependencies>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-beans</artifactId>
  10. <version>5.0.8.RELEASE</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework</groupId>
  14. <artifactId>spring-context</artifactId>
  15. <version>5.0.8.RELEASE</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework</groupId>
  19. <artifactId>spring-core</artifactId>
  20. <version>5.0.8.RELEASE</version>
  21. </dependency>
  22. </dependencies>
  23. </project>


Animal.java:

  1. package org.springframework.example;
  2.  
  3. public interface Animal {
  4. public String makeSound();
  5. }
  6.  

Cat.java:

  1. package org.springframework.example;
  2.  
  3. public class Cat implements Animal{
  4.  
  5. public String makeSound() {
  6. // TODO Auto-generated method stub
  7. return "Meow Meow Meow~";
  8. }
  9.  
  10. }

Dog.java:

  1. package org.springframework.example;
  2.  
  3. public class Dog implements Animal{
  4.  
  5. public String makeSound() {
  6. // TODO Auto-generated method stub
  7. return "Woof Woof Woof~";
  8. }
  9.  
  10. }

Pet.java:

  1. package org.springframework.example;
  2.  
  3. public class Pet {
  4. private Cat cat;
  5. private Dog dog;
  6.  
  7. public Pet(Cat cat, Dog dog) {
  8. this.dog = dog;
  9. this.cat = cat;
  10. }
  11. public Cat getCat() {
  12. return cat;
  13. }
  14.  
  15. public void setCat(Cat cat) {
  16. this.cat = cat;
  17. }
  18.  
  19. public Dog getDog() {
  20. return dog;
  21. }
  22.  
  23. public void setDog(Dog dog) {
  24. this.dog = dog;
  25. }
  26.  
  27. public void playWithCat() {
  28. System.out.println(this.cat.makeSound());
  29. }
  30. public void playWithDog() {
  31. System.out.println(this.dog.makeSound());
  32. }
  33. }

App.java:

  1. package org.springframework.example;
  2.  
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5.  
  6. public class App {
  7.  
  8. public static void main(String[] args) {
  9. ApplicationContext context = new ClassPathXmlApplicationContext("org/springframework/example/beans/beans.xml");
  10. Pet pet = (Pet) context.getBean("pet");
  11. pet.playWithCat();
  12. pet.playWithDog();
  13. ((ClassPathXmlApplicationContext)context).close();
  14. }
  15.  
  16. }


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="cat" class="org.springframework.example.Cat"></bean>
  8. <bean id="dog" class="org.springframework.example.Dog"></bean>
  9. <bean id="pet" class="org.springframework.example.Pet"></bean>
  10. </beans>
  11.  
  12.  
基本程式大致如上,到這邊程式是沒辦法正常執行的,因為還沒設定Spring bean 注入方式。


1. 透過 @Autowired 來自動綁定

@Autowired 會有一套自己的注入流程,一開始會先嘗試 byType,如果遇到 ambiguous的情況再改 byName的方式,最後都找不到匹配對象才會拋出Exception,不過後面會提到可以用更多的方式來幫助Spring找到適合的bean 做注入,減少這類 ambiguous的情況發生。

要透過Annotation來做到自動綁定,在 beans.xml的 namespace需要啟用 context:

beans.xml
啟用後會看到下方頁籤多了一個 context,接著切換到 context,在 root beans上點右鍵,並加入 annotation-config:

beans.xml
加入後會自動在xml加入設定
啟用這個設定後,才能在程式內使用Spring提供之 Annotation 相關設定。

實際上加入context:annotation-config是在Spring中註冊了AutowiredAnnotationBeanPostProcessor﹑ CommonAnnotationBeanPostProcessor﹑ PersistenceAnnotationBeanPostProcessorRequiredAnnotationBeanPostProcessor
目的是要讓程式可以是別不同的Annotation,如加入了AutowiredAnnotationBeanPostProcessor 後程式才可以識別 @Autowire annotation。


透過@Autowired可以有三種設定方式:

1. 透過 setter注入:


第一種方式可以在 setter上加上 @Autowired:

Pet.java


2. 透過 constructor 注入:


第二種方式是在 constructor 上加上 @Autowired:

Pet.java

3.  透過property注入:


第三種方式是直接在要注入的property上加上 @Autowired,而且不需要對應的 setter即可注入,這邊調整一下程式碼:


  1. package org.springframework.example;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4.  
  5. public class Pet {
  6.  
  7. @Autowired
  8. private Cat cat;
  9. @Autowired
  10. private Dog dog;
  11.  
  12. public Cat getCat() {
  13. return cat;
  14. }
  15.  
  16. public Dog getDog() {
  17. return dog;
  18. }
  19.  
  20. public void playWithCat() {
  21. System.out.println(this.cat.makeSound());
  22. }
  23.  
  24. public void playWithDog() {
  25. System.out.println(this.dog.makeSound());
  26. }
  27.  
  28. }

程式的部分把 constructor與 setter移除掉,只在要注入的 property上加上annotation即可。

4. 混搭:


第四種就是結合前面三種混和使用,如透過 constructor與 property一起使用也是可行的:

  1. package org.springframework.example;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4.  
  5. public class Pet {
  6.  
  7. private Cat cat;
  8.  
  9. @Autowired
  10. private Dog dog;
  11.  
  12. @Autowired
  13. public Pet(Cat cat) {
  14. this.cat = cat;
  15. }
  16.  
  17. public Cat getCat() {
  18. return cat;
  19. }
  20.  
  21. public Dog getDog() {
  22. return dog;
  23. }
  24.  
  25. public void playWithCat() {
  26. System.out.println(this.cat.makeSound());
  27. }
  28.  
  29. public void playWithDog() {
  30. System.out.println(this.dog.makeSound());
  31. }
  32.  
  33. }


使用 Autowired 時Spring 會檢查要注入的目標是否有正常的被注入,如果沒有,則會拋出Exception,假使要注入的目標並非一定要注入東西,可以在 @Autowired上加入 required = false, Spring 看到這個設定後,如果目標在注入時因為一些原因無法注入, Spring 就會忽略這個錯誤。

如:
  1. @Autowired(required = false)
  2. private Dog dog;
  3.  


2. Qualifiers

使用Qualifiers可以有效幫助Spring 識別要注入的 bean,主要是在bean 定義上加上Qualifier資訊,這邊稍微調整程式,刻意產生一個 Spring 認為 ambiguous的情況:

Pet.java:

  1. package org.springframework.example;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4.  
  5. public class Pet {
  6. @Autowired
  7. private Cat cat;
  8.  
  9. @Autowired
  10. private Dog dog;
  11.  
  12. public Cat getCat() {
  13. return cat;
  14. }
  15.  
  16. public Dog getDog() {
  17. return dog;
  18. }
  19.  
  20. public void playWithCat() {
  21. System.out.println(this.cat.makeSound());
  22. }
  23.  
  24. public void playWithDog() {
  25. System.out.println(this.dog.makeSound());
  26. }
  27.  
  28. }

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. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  6. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
  7.  
  8. <bean id="smallCat" class="org.springframework.example.Cat"></bean>
  9. <bean id="bigCat" class="org.springframework.example.Cat"></bean>
  10. <bean id="dog" class="org.springframework.example.Dog"></bean>
  11. <bean id="pet" class="org.springframework.example.Pet"></bean>
  12. <context:annotation-config></context:annotation-config>
  13. </beans>
  14.  
這邊調整beans.xml,讓beans.xml中存在兩個Cat 類別,id 分別為 bigCat 與smallCat,在 runtime 時Spring 會拋出 Exception,因為無法判斷要注入哪一個Cat,當然我們可以將變數 cat 改名為 smallCat或是 bigCat就可以排除問題(autowired byName),但這邊嘗試用 Qualifier解決。

我們可以在 beans.xml 的bean上加上 qualifier 資訊:


beans.xml

beans.xml
分別為兩個 cat bean加上 qualifier:bigCat 與 smallCat

接著在 Pet.java上也加入qualifier annotation,目的在告訴Spring要找qualifier 為 smallCat的 Cat類別:

Pet.java

如此一來,Spring就可以知道諸多的Cat類別中,要找smallCat類別作為注入來源。


除了在 beans.xml 中定義 qualifier,也可以在類別上直接定義一個 Qualifier,調整一下程式碼:

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. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  6. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
  7.  
  8. <bean id="smallCat" class="org.springframework.example.Cat"></bean>
  9. <bean id="dog" class="org.springframework.example.Dog"></bean>
  10. <bean id="pet" class="org.springframework.example.Pet"></bean>
  11. <context:annotation-config></context:annotation-config>
  12. </beans>


Pet.java

  1. package org.springframework.example;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.beans.factory.annotation.Qualifier;
  5.  
  6. public class Pet {
  7. @Autowired
  8. @Qualifier("Cat")
  9. private Animal animal;
  10.  
  11. @Autowired
  12. private Dog dog;
  13.  
  14. public void playWithCat() {
  15. System.out.println(this.animal.makeSound());
  16. }
  17.  
  18. public void playWithDog() {
  19. System.out.println(this.dog.makeSound());
  20. }
  21.  
  22. }

原本為Cat 類別之變數,現改為Animal類別,而 Cat與 Dog皆繼承自 Animal 類別,我們在animal 變數上加上 Qualifier,指明 Cat,接著調整Cat類別:

Cat.java:

  1. package org.springframework.example;
  2.  
  3. import org.springframework.beans.factory.annotation.Qualifier;
  4.  
  5. @Qualifier("Cat")
  6. public class Cat implements Animal{
  7.  
  8. public String makeSound() {
  9. // TODO Auto-generated method stub
  10. return "Meow Meow Meow~";
  11. }
  12.  
  13. }
  14.  

在Cat 類別上方加上 Qualifier,設定好後,在runtime Spring 就會知道要將Cat 注入到 animal變數中。

3. 使用 @Resource Annotation

Resource的定義在 JSR-250中,可以參考Wiki 上說明:JSR-250 ,會提到正是因為與 dependency有關。

Resource Annotation 可以如同 Autowired Annotation一樣,做到 Dependency Injection,參考下列程式碼:

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. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  6. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
  7.  
  8. <bean id="cat" class="org.springframework.example.Cat"></bean>
  9. <bean id="dog" class="org.springframework.example.Dog"></bean>
  10. <bean id="pet" class="org.springframework.example.Pet"></bean>
  11. <context:annotation-config></context:annotation-config>
  12. </beans>
  13.  
將 beans.xml調回最初的設定。

Pet.java:

  1. package org.springframework.example;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import javax.annotation.Resource;
  5.  
  6. public class Pet {
  7. @Resource(name = "cat")
  8. private Animal animal;
  9.  
  10. @Autowired
  11. private Dog dog;
  12.  
  13. public void playWithCat() {
  14. System.out.println(this.animal.makeSound());
  15. }
  16.  
  17. public void playWithDog() {
  18. System.out.println(this.dog.makeSound());
  19. }
  20.  
  21. }
  22.  

這邊將 animal 注入的 @Autowired 改為 @Resource,使用 Resource 需要指明要注入的bean 名稱,如此即可在 animal注入 Cat 類別。

@Autowired 與 @Resource 做的事情是相同,差異在於 Spring 在選擇注入來源時的順序,在這一篇文章中有不錯的解釋,簡單來說,

@Autowired 會先 byType -> Qualifier -> byName
@Resource 則是 byName -> byType -> Qualifier

4. 使用 @Inject

與 @Resource與 @Autowired 相似,都可以用來做 Spring 的 Dependency Injection, @Inject 定義於 JSR330,要使用 @Inject,需要在 pom.xml 上額外加入 javax.inject dependency:

pom.xml

beans.xml 與前一個部分相同,這邊僅修改 Pet.java,直接看使用方式:

  1. package org.springframework.example;
  2.  
  3. import javax.inject.Inject;
  4. import javax.inject.Named;
  5.  
  6. import org.springframework.beans.factory.annotation.Autowired;
  7.  
  8. public class Pet {
  9. @Inject
  10. @Named("cat")
  11. private Animal animal;
  12.  
  13. @Autowired
  14. private Dog dog;
  15.  
  16. public void playWithCat() {
  17. System.out.println(this.animal.makeSound());
  18. }
  19.  
  20. public void playWithDog() {
  21. System.out.println(this.dog.makeSound());
  22. }
  23.  
  24. }

正常來說在 animal 上加入 @Inject 即可,但因為這邊有 ambiguous的問提,所以我們可以加上 Named annotation,指定要用 id 為 cat 的bean,就可以正常的執行程式。

@Inject、@Resource 與 Autowired 做的事情都是相同的,只是使用上有些許不同,這邊僅記錄簡單的部分,實際深入還需要額外看說明

5. 讓Spring 掃描可注入的Bean

到現在所有讓Spring注入的來源都是定義在 beans.xml中,這邊將介紹如何透過 annotation設定,讓 Spring 可以找尋特定目錄中可注入的 bean,省掉在 beans.xml 中定義的步驟:

beans.xml
而加入 context:component-scan 後,需要指定 base-package內容是要讓 Spring 掃描的package,如此一來 Spring 才知道要到哪個目錄找透過 annotation定義好的 bean。此外,加入context:component-scan後就可以將 context:annotation-config 移除,因為 context:annotation-config 向 Spring 註冊的東西,context:component-scan也會做,在這部分重複了,就可以移除掉。

同時可以將 beans.xml 中所有 bean 定義都移除掉:

  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. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  6. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
  7.  
  8. <context:component-scan
  9. base-package="org.springframework.example">
  10. </context:component-scan>
  11. </beans>
  12.  

接著程式碼的部分:

Cat.java

  1. package org.springframework.example;
  2.  
  3. import org.springframework.stereotype.Component;
  4.  
  5. @Component(value = "cat")
  6. public class Cat implements Animal{
  7.  
  8. public String makeSound() {
  9. // TODO Auto-generated method stub
  10. return "Meow Meow Meow~";
  11. }
  12.  
  13. }
  14.  

Dog.java

  1. package org.springframework.example;
  2.  
  3. import org.springframework.stereotype.Component;
  4.  
  5. @Component
  6. public class Dog implements Animal{
  7.  
  8. public String makeSound() {
  9. // TODO Auto-generated method stub
  10. return "Woof Woof Woof~";
  11. }
  12.  
  13. }
  14.  

Pet.java

  1. package org.springframework.example;
  2.  
  3. import javax.inject.Inject;
  4. import javax.inject.Named;
  5.  
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Component;
  8.  
  9. @Component
  10. public class Pet {
  11. @Inject
  12. @Named("cat")
  13. private Animal animal;
  14.  
  15. @Autowired
  16. private Dog dog;
  17.  
  18. public void playWithCat() {
  19. System.out.println(this.animal.makeSound());
  20. }
  21.  
  22. public void playWithDog() {
  23. System.out.println(this.dog.makeSound());
  24. }
  25.  
  26. }
  27.  

這邊簡單說明, 在類別上加上 @Component 就是讓Spring 知道這個類別可以作為 bean 注入的來源,而在 Component內定義 value 等同是在 beans.xml 中定義的 id。

因此原本在 beans.xml 中的3個 bean,只要在原本類別上加上 @Component,Spring 就會拿來作為注入來源,因此 beans.xml 中的定義就可以移除掉,執行後的結果會與先前相同。


6. 透過 annotation 注入 bean property

beans.xml 中可以設定 bean的 property value,annotation也可以,假使現在要在 Cat 類別上加上 name 屬性,並透過 annotation 設定內容,可以透過下列方式做到:

Cat.java:

  1. package org.springframework.example;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.beans.factory.annotation.Value;
  5. import org.springframework.stereotype.Component;
  6.  
  7. @Component(value = "cat")
  8. public class Cat implements Animal {
  9.  
  10. private String name;
  11.  
  12. @Autowired
  13. public void setName(@Value(value = "Kitty") String name) {
  14. this.name = name;
  15. }
  16. public Object getName() {
  17. return this.name;
  18. }
  19.  
  20. public String makeSound() {
  21. // TODO Auto-generated method stub
  22. return "Meow Meow Meow~";
  23. }
  24.  
  25. }

要做到 Property Injection,基本上要用 setter來做到,並透過 @Value annotation來設定注入內容,同時搭配上 @Autowired,即可以對 Cat 類別的 name 做注入。

如果要注入其他的 bean 到 Cat中,只要使用 Autowired 即可,如同 Pet.java 的作法。

7. Annotation 中的 init 與 destroy:@PostConstruct & @PreDestroy

透過 Annotation 要做到 bean 初始化時執行與結束時執行某些事情,可以在 method 前面分別加上@PostConstruct & @PreDestroy,分別對應到 beans.xml 中的 init-method與destroy-method。

這邊在Cat類別上加上一個初始化後執行的 method 與結束時要做的 method:

  1. package org.springframework.example;
  2.  
  3. import javax.annotation.PostConstruct;
  4. import javax.annotation.PreDestroy;
  5.  
  6. import org.springframework.stereotype.Component;
  7.  
  8. @Component(value = "cat")
  9. public class Cat implements Animal {
  10.  
  11. public String makeSound() {
  12. // TODO Auto-generated method stub
  13. return "Meow Meow Meow~";
  14. }
  15. @PostConstruct
  16. public void init() {
  17. System.out.println("Cat init");
  18. }
  19. @PreDestroy
  20. public void destroy() {
  21. System.out.println("Cat destroy");
  22. }
  23.  
  24. }
  25.  

執行結果:

執行結果


結語

Spring annotation 設定部分大致上到這邊,接著會試著使用Spring 的 SPEL,SPEL 也可以搭配著 bean的 dependenc injection使用。

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





沒有留言:

張貼留言

Java Spring Framework 筆記 - Autowiring (2)

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