Java Spring Framework 筆記 - Autowiring (2)

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





先上程式碼:

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
啟用後會看到下方頁籤多了一個 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即可注入,這邊調整一下程式碼:


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
分別為兩個 cat bean加上 qualifier:bigCat 與 smallCat

接著在 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
而加入 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 定義都移除掉:

<?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課程後所做的一些筆記,如果有興趣,可以參考這門課程。





沒有留言:

張貼留言

Java Spring Framework 筆記 - Autowiring (2)

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