Spring构造函数注入方式详解

  本文讲解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. 参数依赖类型有多实现类情况下的注入方式

单构造函数参数依赖的类型,有多个实现类时,就不能直接像上面的例子一样,只定义接口的类型了:

以下方式是错误的:

public MorePlayContorller(MorePlay morePlay) {
morePlay.someOnePlay();
}

需要写明所引用的bean的名称,否则spring根据type匹配到两个bean,就会报错。

看下实际的例子:

扫描二维码关注公众号,回复: 5776371 查看本文章
  • 声明一个接口:
    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。

  • 接口和实现类,我们继续沿用MorePlay及其实现。

  • 依赖的类定义如下:
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());
    }

}
  • listPlay方法会执行GoPlay接口所有实现类对方法havePlay()的重写。list的注入方式易于业务的扩展,封装的代码不会因为扩展了一个新的实现类而发生改动,完全遵循了设计模式的原则。

  • 用测试用例验证一下:
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.

猜你喜欢

转载自blog.51cto.com/14277129/2374078