先上程式碼:
pom.xml:
<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"> <modelVersion>4.0.0</modelVersion> <groupId>org.springframework.example</groupId> <artifactId>spring-annotation-autowiring</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.0.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.0.8.RELEASE</version> </dependency> </dependencies> </project>
Animal.java:
package org.springframework.example; public interface Animal { public String makeSound(); }
Cat.java:
package org.springframework.example; public class Cat implements Animal{ public String makeSound() { // TODO Auto-generated method stub return "Meow Meow Meow~"; } }
Dog.java:
package org.springframework.example; public class Dog implements Animal{ public String makeSound() { // TODO Auto-generated method stub return "Woof Woof Woof~"; } }
Pet.java:
package org.springframework.example; public class Pet { private Cat cat; private Dog dog; public Pet(Cat cat, Dog dog) { this.dog = dog; this.cat = cat; } public Cat getCat() { return cat; } public void setCat(Cat cat) { this.cat = cat; } public Dog getDog() { return dog; } public void setDog(Dog dog) { this.dog = dog; } public void playWithCat() { System.out.println(this.cat.makeSound()); } public void playWithDog() { System.out.println(this.dog.makeSound()); } }
App.java:
package org.springframework.example; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("org/springframework/example/beans/beans.xml"); Pet pet = (Pet) context.getBean("pet"); pet.playWithCat(); pet.playWithDog(); ((ClassPathXmlApplicationContext)context).close(); } }
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="cat" class="org.springframework.example.Cat"></bean> <bean id="dog" class="org.springframework.example.Dog"></bean> <bean id="pet" class="org.springframework.example.Pet"></bean> </beans>基本程式大致如上,到這邊程式是沒辦法正常執行的,因為還沒設定Spring bean 注入方式。
1. 透過 @Autowired 來自動綁定
@Autowired 會有一套自己的注入流程,一開始會先嘗試 byType,如果遇到 ambiguous的情況再改 byName的方式,最後都找不到匹配對象才會拋出Exception,不過後面會提到可以用更多的方式來幫助Spring找到適合的bean 做注入,減少這類 ambiguous的情況發生。要透過Annotation來做到自動綁定,在 beans.xml的 namespace需要啟用 context:
beans.xml |
beans.xml |
加入後會自動在xml加入設定 |
實際上加入context:annotation-config是在Spring中註冊了AutowiredAnnotationBeanPostProcessor﹑ CommonAnnotationBeanPostProcessor﹑ PersistenceAnnotationBeanPostProcessor與RequiredAnnotationBeanPostProcessor,
目的是要讓程式可以是別不同的Annotation,如加入了AutowiredAnnotationBeanPostProcessor 後程式才可以識別 @Autowire annotation。
透過@Autowired可以有三種設定方式:
1. 透過 setter注入:
第一種方式可以在 setter上加上 @Autowired:
Pet.java |
2. 透過 constructor 注入:
第二種方式是在 constructor 上加上 @Autowired:
Pet.java |
3. 透過property注入:
第三種方式是直接在要注入的property上加上 @Autowired,而且不需要對應的 setter即可注入,這邊調整一下程式碼:
package org.springframework.example; import org.springframework.beans.factory.annotation.Autowired; public class Pet { @Autowired private Cat cat; @Autowired private Dog dog; public Cat getCat() { return cat; } public Dog getDog() { return dog; } public void playWithCat() { System.out.println(this.cat.makeSound()); } public void playWithDog() { System.out.println(this.dog.makeSound()); } }
程式的部分把 constructor與 setter移除掉,只在要注入的 property上加上annotation即可。
4. 混搭:
第四種就是結合前面三種混和使用,如透過 constructor與 property一起使用也是可行的:
package org.springframework.example; import org.springframework.beans.factory.annotation.Autowired; public class Pet { private Cat cat; @Autowired private Dog dog; @Autowired public Pet(Cat cat) { this.cat = cat; } public Cat getCat() { return cat; } public Dog getDog() { return dog; } public void playWithCat() { System.out.println(this.cat.makeSound()); } public void playWithDog() { System.out.println(this.dog.makeSound()); } }
使用 Autowired 時Spring 會檢查要注入的目標是否有正常的被注入,如果沒有,則會拋出Exception,假使要注入的目標並非一定要注入東西,可以在 @Autowired上加入 required = false, Spring 看到這個設定後,如果目標在注入時因為一些原因無法注入, Spring 就會忽略這個錯誤。
如:
@Autowired(required = false) private Dog dog;
2. Qualifiers
使用Qualifiers可以有效幫助Spring 識別要注入的 bean,主要是在bean 定義上加上Qualifier資訊,這邊稍微調整程式,刻意產生一個 Spring 認為 ambiguous的情況:Pet.java:
package org.springframework.example; import org.springframework.beans.factory.annotation.Autowired; public class Pet { @Autowired private Cat cat; @Autowired private Dog dog; public Cat getCat() { return cat; } public Dog getDog() { return dog; } public void playWithCat() { System.out.println(this.cat.makeSound()); } public void playWithDog() { System.out.println(this.dog.makeSound()); } }
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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <bean id="smallCat" class="org.springframework.example.Cat"></bean> <bean id="bigCat" class="org.springframework.example.Cat"></bean> <bean id="dog" class="org.springframework.example.Dog"></bean> <bean id="pet" class="org.springframework.example.Pet"></bean> <context:annotation-config></context:annotation-config> </beans>這邊調整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 |
接著在 Pet.java上也加入qualifier annotation,目的在告訴Spring要找qualifier 為 smallCat的 Cat類別:
Pet.java |
如此一來,Spring就可以知道諸多的Cat類別中,要找smallCat類別作為注入來源。
除了在 beans.xml 中定義 qualifier,也可以在類別上直接定義一個 Qualifier,調整一下程式碼:
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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <bean id="smallCat" class="org.springframework.example.Cat"></bean> <bean id="dog" class="org.springframework.example.Dog"></bean> <bean id="pet" class="org.springframework.example.Pet"></bean> <context:annotation-config></context:annotation-config> </beans>
Pet.java
package org.springframework.example; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; public class Pet { @Autowired @Qualifier("Cat") private Animal animal; @Autowired private Dog dog; public void playWithCat() { System.out.println(this.animal.makeSound()); } public void playWithDog() { System.out.println(this.dog.makeSound()); } }
原本為Cat 類別之變數,現改為Animal類別,而 Cat與 Dog皆繼承自 Animal 類別,我們在animal 變數上加上 Qualifier,指明 Cat,接著調整Cat類別:
Cat.java:
package org.springframework.example; import org.springframework.beans.factory.annotation.Qualifier; @Qualifier("Cat") public class Cat implements Animal{ public String makeSound() { // TODO Auto-generated method stub return "Meow Meow Meow~"; } }
在Cat 類別上方加上 Qualifier,設定好後,在runtime Spring 就會知道要將Cat 注入到 animal變數中。
3. 使用 @Resource Annotation
Resource的定義在 JSR-250中,可以參考Wiki 上說明:JSR-250 ,會提到正是因為與 dependency有關。Resource Annotation 可以如同 Autowired Annotation一樣,做到 Dependency Injection,參考下列程式碼:
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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <bean id="cat" class="org.springframework.example.Cat"></bean> <bean id="dog" class="org.springframework.example.Dog"></bean> <bean id="pet" class="org.springframework.example.Pet"></bean> <context:annotation-config></context:annotation-config> </beans>將 beans.xml調回最初的設定。
Pet.java:
package org.springframework.example; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.Resource; public class Pet { @Resource(name = "cat") private Animal animal; @Autowired private Dog dog; public void playWithCat() { System.out.println(this.animal.makeSound()); } public void playWithDog() { System.out.println(this.dog.makeSound()); } }
這邊將 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,直接看使用方式:
package org.springframework.example; import javax.inject.Inject; import javax.inject.Named; import org.springframework.beans.factory.annotation.Autowired; public class Pet { @Inject @Named("cat") private Animal animal; @Autowired private Dog dog; public void playWithCat() { System.out.println(this.animal.makeSound()); } public void playWithDog() { System.out.println(this.dog.makeSound()); } }
正常來說在 animal 上加入 @Inject 即可,但因為這邊有 ambiguous的問提,所以我們可以加上 Named annotation,指定要用 id 為 cat 的bean,就可以正常的執行程式。
@Inject、@Resource 與 Autowired 做的事情都是相同的,只是使用上有些許不同,這邊僅記錄簡單的部分,實際深入還需要額外看說明。
5. 讓Spring 掃描可注入的Bean
到現在所有讓Spring注入的來源都是定義在 beans.xml中,這邊將介紹如何透過 annotation設定,讓 Spring 可以找尋特定目錄中可注入的 bean,省掉在 beans.xml 中定義的步驟:beans.xml |
同時可以將 beans.xml 中所有 bean 定義都移除掉:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <context:component-scan base-package="org.springframework.example"> </context:component-scan> </beans>
接著程式碼的部分:
Cat.java
package org.springframework.example; import org.springframework.stereotype.Component; @Component(value = "cat") public class Cat implements Animal{ public String makeSound() { // TODO Auto-generated method stub return "Meow Meow Meow~"; } }
Dog.java
package org.springframework.example; import org.springframework.stereotype.Component; @Component public class Dog implements Animal{ public String makeSound() { // TODO Auto-generated method stub return "Woof Woof Woof~"; } }
Pet.java
package org.springframework.example; import javax.inject.Inject; import javax.inject.Named; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class Pet { @Inject @Named("cat") private Animal animal; @Autowired private Dog dog; public void playWithCat() { System.out.println(this.animal.makeSound()); } public void playWithDog() { System.out.println(this.dog.makeSound()); } }
這邊簡單說明, 在類別上加上 @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:
package org.springframework.example; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component(value = "cat") public class Cat implements Animal { private String name; @Autowired public void setName(@Value(value = "Kitty") String name) { this.name = name; } public Object getName() { return this.name; } public String makeSound() { // TODO Auto-generated method stub return "Meow Meow Meow~"; } }
要做到 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:
package org.springframework.example; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.springframework.stereotype.Component; @Component(value = "cat") public class Cat implements Animal { public String makeSound() { // TODO Auto-generated method stub return "Meow Meow Meow~"; } @PostConstruct public void init() { System.out.println("Cat init"); } @PreDestroy public void destroy() { System.out.println("Cat destroy"); } }
執行結果:
執行結果 |
結語
Spring annotation 設定部分大致上到這邊,接著會試著使用Spring 的 SPEL,SPEL 也可以搭配著 bean的 dependenc injection使用。這邊都是上這門 udemy課程後所做的一些筆記,如果有興趣,可以參考這門課程。
沒有留言:
張貼留言