本文講解spirng三種注入方式(構造函數注入,setter注入,注解注入)中的構造函數注入。
所有例子,都是以注解的方式注冊bean。
關于構造函數方式注入,spring官網的說明位址為:Spring官網之構造函數注入
1. 構造函數隻有一個參數且類型為單個實作類
單參數且單實作類這是一種最簡單的以構造函數的注入方式注入依賴,隻要在構造函數中添加所依賴類型的參數即可,spring會比對對應類型的bean進行注入,由于隻有一個對應類型的實作類,是以能準确地找到bean進行注入。
我們看以下例子:
- 建立一個接口:
public interface GoPlay {
public void havePlay();
}
- 建立一個實作類
import org.springframework.stereotype.Component;
@Component
public class GoPlayImpl implements GoPlay {
@Override
public void havePlay() {
System.out.println("\n\nsingle play\n\n");
}
}
- 再建立一個具體類,依賴于GoPlay接口,以構造函數方式注入:
import org.springframework.stereotype.Component;
@Component
public class PlayController {
private GoPlay goPlay;
public PlayController(GoPlay goPlay) {
this.goPlay = goPlay;
}
public void play(){
goPlay.havePlay();
}
}
- 用單元測試驗證一下是否能将GoPlay唯一的實作類注入到PlayController中:
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@RunWith(SpringRunner.class)
@SpringBootTest
public class PlayControllerTest {
@Resource
PlayController playController;
@After
public void tearDown() throws Exception {
}
@Test
public void play() {
playController.play();
}
}
//有一些自動生成的函數沒有删除
- 執行測試用例,執行結果列印如下:
single play
說明成功地将對應的bean以構造函數的方式注入。
2. 參數依賴類型有多實作類情況下的注入方式
單構造函數參數依賴的類型,有多個實作類時,就不能直接像上面的例子一樣,隻定義接口的類型了:
以下方式是錯誤的:需要寫明所引用的bean的名稱,否則spring根據type比對到兩個bean,就會報錯。public MorePlayContorller(MorePlay morePlay) { morePlay.someOnePlay(); }
看下實際的例子:
- 聲明一個接口:
public interface MorePlay { public void someOnePlay(); }
- 第一個實作類:
import org.springframework.stereotype.Component;
@Component
public class MorePlayImplFirstOne implements MorePlay {
@Override
public void someOnePlay() {
System.out.println("\n\nFirst one play.\n\n");
}
}
- 第二個實作類:
import org.springframework.stereotype.Component;
@Component
public class MorePlayImplSecondOne implements MorePlay {
@Override
public void someOnePlay() {
System.out.println("\n\nSecond one play.\n\n");
}
}
- 以構造函數方式注入以上定義類型(下面這個例子會注入失敗):
import org.springframework.stereotype.Component;
@Component
public class MorePlayContorller {
private MorePlay morePlay;
public MorePlayContorller(MorePlay morePlay) {
morePlay.someOnePlay();
}
}
- 寫一個測試用例驗證一下:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MorePlayContorllerTest {
@Resource MorePlayContorller morePlayContorller;
@Test
public void play() {
morePlayContorller.play();
}
}
- 執行結果如下(堆棧這裡就不貼了,看一下具體的報錯):
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.springbootexample.oneArgument.MultiImplementation.MorePlay' available: expected single matching bean but found 2: morePlayImplFirstOne,
很顯然,直接就懵圈了,我找到了兩個,你是想要哪一個?實際上,這種方式編譯都過不了。
-
正确的方式:用Qualifier指定具體的bean name,或者構造函數中的屬性名與所要注入的bean名稱一緻。
方式一:用Qualifier指定具體的bean name
@Component
public class MorePlayContorller {
private MorePlay morePlay;
public MorePlayContorller(@Qualifier("morePlayImplFirstOne") MorePlay morePlay) {
this.morePlay = morePlay;
}
public void play(){
morePlay.someOnePlay();
}
}
方式二:構造函數中的屬性名與所要注入的bean名稱一緻
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class MorePlayContorller {
private MorePlay morePlay;
public MorePlayContorller(@Qualifier("morePlayImplFirstOne") MorePlay morePlay) {
this.morePlay = morePlay;
}
public void play(){
morePlay.someOnePlay();
}
}
- 以上兩種方式都正确注入,再執行測試用例,就能執行成功了:
First one play
3. list注入方式
當具體業務場景中,需要依賴于某接口的所有實作類時,可以使用list注入,構造函數方式注入,同樣也可以注入list。
- 依賴的類定義如下:
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class ListPlayControler {
private List<MorePlay> goPlays;
public ListPlayControler(List<MorePlay> goPlays) {
this.goPlays = goPlays;
}
public void listPlay(){
goPlays.forEach(goPlay -> goPlay.someOnePlay());
}
}
- 用測試用例驗證一下:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ListPlayControlerTest {
@Resource private ListPlayControler listPlayControler;
@Test
public void listPlay() {
listPlayControler.listPlay();
}
}
- 執行結果符合預期:
First one play.
Second one play.
4. 構造函數多參數依賴情況下的注入方式
- 我們建立一個類,使他依賴于本文中建立的兩個接口:
import com.example.springbootexample.oneArgument.MultiImplementation.MorePlay;
import com.example.springbootexample.oneArgument.SingleImplementation.GoPlay;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class PlayMoreArugumentContoller {
private GoPlay goPlay;
private MorePlay morePlay;
public PlayMoreArugumentContoller(GoPlay goPlay, @Qualifier("morePlayImplSecondOne") MorePlay morePlay) {
this.goPlay = goPlay;
this.morePlay = morePlay;
}
public void playAll(){
goPlay.havePlay();
morePlay.someOnePlay();
}
}
- 建立測試用例驗證一下,執行成功,能夠成功注入對應的bean:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@RunWith(SpringRunner.class)
@SpringBootTest
public class PlayMoreArugumentContollerTest {
@Resource private PlayMoreArugumentContoller playMoreArugumentContoller;
@Test
public void playAll() {
playMoreArugumentContoller.playAll();
}
}
single play
Second one play.