解释为什么构造函数注入比其他选项更好

时间:2014-01-19 15:43:33

标签: java spring dependency-injection required

在Pro Spring 3 Book中, 第4章 - 介绍Spring中的IOC和DI - 第59页,在“Setter Injection vs. Constructor Injection”部分中,一段说明

  

包含Spring,提供一种机制来确保在何时定义所有依赖项   你使用Setter Injection,但是通过使用Constructor Injection,你以容器无关的方式断言对依赖的要求“

你能解释一下

吗?

7 个答案:

答案 0 :(得分:75)

只有提供了该参数才能实例化一个必需的依赖项作为构造函数参数的类(你应该有一个guard子句来确保参数不为null。)因此,构造函数强制执行依赖性要求你正在使用Spring,使其与容器无关。

如果使用setter注入,则可能会调用或不调用setter,因此实例永远不会提供其依赖项。强制调用setter的唯一方法是使用@Required@Autowired ,这是特定于Spring的,因此不是与容器无关的。

因此,为了保持代码独立于Spring,请使用构造函数参数进行注入。

更新:Spring 4.3将执行implicit injection in single-constructor scenarios,使您的代码更加独立于Spring,可能根本不需要@Autowired注释。

答案 1 :(得分:15)

  
    

(...)通过使用构造函数注入,您以容器无关的方式声明对依赖项的要求

  

这意味着您可以在不使用任何容器特定解决方案的情况下强制执行所有注入字段的要求


Setter注射示例

使用setter注入需要特殊的弹簧注释@Required

  

<强> @Required

     

将方法(通常是JavaBean setter方法)标记为“ required ”:也就是说,必须将setter方法配置为依赖注入值。

<强>用法

import org.springframework.beans.factory.annotation.Required;

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class Foo {

    private Bar bar;

    @Inject
    @Required
    public void setBar(Bar bar) {
        this.bar = bar;
    }
}

构造函数注入示例

所有必填字段都在构造函数中定义,纯Java解决方案。

<强>用法

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class Foo {

    private Bar bar;

    @Inject
    public Foo(Bar bar) {
        this.bar = bar;
    }

}

答案 2 :(得分:11)

为了简单起见,让我们说我们可以使用基于构造函数的依赖注入来实现强制依赖,并使用基于setter的注入来实现可选的依赖。这是一个经验法则!!

让我们举个例子。

如果要实例化一个类,则始终使用其构造函数进行实例化。因此,如果您使用基于构造函数的注入,则实例化该类的唯一方法是通过该构造函数。如果通过构造函数传递依赖关系,很明显它是一个强制依赖。

另一方面,如果POJO类中有setter方法,则可以使用该setter方法为类变量设置或不设置值。它完全基于您的需要。即它是可选的。因此,如果通过类的setter方法传递依赖关系,则隐式意味着它是一个可选的依赖项。希望这很清楚!!

答案 3 :(得分:5)

如果没有依赖类,类无法运行,则使用构造函数注入。

当类可以在没有依赖类的情况下运行时,使用属性注入。

作为一个具体的例子,考虑一个依赖于IService来完成其工作的ServiceRepository。由于ServiceRepository在没有IService的情况下无法正常运行,因此通过构造函数注入它是有意义的。

相同的ServiceRepository类可以使用Logger进行跟踪。 ILogger可以通过Property注入注入。

Property注入的其他常见示例是ICache(AOP术语的另一个方面)或IBaseProperty(基类中的属性)。

答案 4 :(得分:4)

  

通过使用构造函数注入,您以容器无关的方式声明对依赖项的要求

我们需要来自IoC容器的保证,在使用任何bean之前,必须注入必要的bean。

setter injection 策略中,我们相信它将首先创建bean的IoC容器,但是在使用setter方法使用bean之前会立即执行注入。注射是根据您的配置完成的。如果你以某种方式错过指定要在配置中注入的任何bean,则不会对这些bean进行注入,并且在使用它时,依赖bean将无法相应地运行!

但是在构造函数注入策略中,容器强制(或必须强制)在构造bean时正确地提供依赖关系。这被解决为 “容器无关的方式” ,因为我们需要在创建bean时提供依赖关系,从而使依赖关系的可见性独立于任何IoC容器。

<强> 编辑:

Q1: 以及如何防止容器使用null值而非丢失bean的构造函数创建bean?

你没有选择真正错过任何<constructor-arg>(在Spring的情况下),因为你被IoC容器强加,以提供匹配提供的构造函数所需的所有构造函数参数来创建bean。如果您故意在null提供<constructor-arg>。那么IoC容器无法做或不需要做任何事情!

答案 5 :(得分:3)

这个例子可能会有所帮助:

控制器类:

@RestController
@RequestMapping("/abc/dev")
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class MyController {
//Setter Injection
@Resource(name="configBlack")
public void setColor(Color c) {
    System.out.println("Injecting setter");
    this.blackColor = c;
}

public Color getColor() {
    return this.blackColor;
}

public MyController() {
    super();
}

Color nred;
Color nblack;

//Constructor injection
@Autowired
public MyController(@Qualifier("constBlack")Color b, @Qualifier("constRed")Color r) {
    this.nred = r;
    this.nblack = b;
}

private Color blackColor;

//Field injection
@Autowired
private Color black;

//Field injection
@Resource(name="configRed")
private Color red;

@RequestMapping(value = "/customers", produces = { "application/text" }, method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.CREATED)
public String createCustomer() {
    System.out.println("Field injection red: " + red.getName());
    System.out.println("Field injection: " + black.getName());
    System.out.println("Setter injection black: " + blackColor.getName());

    System.out.println("Constructor inject nred: " + nred.getName());
    System.out.println("Constructor inject nblack: " + nblack.getName());


    MyController mc = new MyController();
    mc.setColor(new Red("No injection red"));
    System.out.println("No injection : " + mc.getColor().getName());

    return "Hello";
}
}

界面颜色:

public interface Color {
    public String getName();
}

红色课程:

@Component
public class Red implements Color{
private String name;

@Override
public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public Red(String name) {
    System.out.println("Red color: "+ name);
    this.name = name;
}

public Red() {
    System.out.println("Red color default constructor");
}

}

班级黑:

@Component
public class Black implements Color{
private String name;

@Override
public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public Black(String name) {
    System.out.println("Black color: "+ name);
    this.name = name;
}

public Black() {
    System.out.println("Black color default constructor");
}

}

用于创建Beans的Config类:

@Configuration
public class Config {

@Bean(name = "configRed")
public Red getRedInstance() {
    Red red = new Red();
    red.setName("Config red");
    return red;
}

@Bean(name = "configBlack")
public Black getBlackInstance() {
    Black black = new Black();
    black.setName("config Black");
    return black;
}

@Bean(name = "constRed")
public Red getConstRedInstance() {
    Red red = new Red();
    red.setName("Config const red");
    return red;
}

@Bean(name = "constBlack")
public Black getConstBlackInstance() {
    Black black = new Black();
    black.setName("config const Black");
    return black;
}
}

BootApplication(主类):

@SpringBootApplication
@ComponentScan(basePackages = {"com"})
public class BootApplication {

public static void main(String[] args) {
    SpringApplication.run(BootApplication.class, args);
}
}
  

运行应用程序并点击网址:   获取127.0.0.1:8080/abc/dev/customers /

Output:
Injecting setter
Field injection red: Config red
Field injection: null
Setter injection black: config Black
Constructor inject nred: Config const red
Constructor inject nblack: config const Black
Red color: No injection red
Injecting setter
No injection : No injection red

答案 6 :(得分:0)

举例说明?这是一个简单的:

public class TwoInjectionStyles {
    private Foo foo;

    // Constructor injection
    public TwoInjectionStyles(Foo f) {
        this.foo = f;
    }

    // Setting injection
    public void setFoo(Foo f) { this.foo = f; }
}

就我个人而言,我个人更喜欢构造函数注入。

在这两种情况下,bean工厂都会实例化TwoInjectionStylesFoo实例,并为前者提供Foo依赖项。