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

在Pro Spring 3 Book中,第4章 – 介绍IOC和DI在Spring中 – 第59页,在“Setter Injection vs. Constructor Injection”一节中,

包含Spring,提供了一种机制来确保在使用Setter Injection时定义所有的依赖关系,但是通过使用Constructor Injection,可以以容器不可知的方式声明对依赖关系的要求“

你能用例子来解释吗?

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

如果使用setter注入,setter可能被调用,也可能不被调用,所以实例可能永远不会被提供依赖。 强制setter被调用的唯一方法是使用@Required@Autowired ,这是Spring特有的,因此不是容器不可知的。

所以要保持你的代码独立于Spring,使用构造函数参数进行注入。

更新 :Spring 4.3将在单构造scheme中执行隐式注入 ,使您的代码更独立于Spring,可能根本不需要@Autowired注解。

(…)通过使用构造器注入,您以容器不可知的方式断言的依赖的要求

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


塞特注射的例子

使用setter注入特殊的注释@Required是必需的。

@需要

将一个方法(通常是JavaBean setter方法)标记为“ 必需 ”:也就是说,必须将setter方法configuration为dependency injection一个值。

用法

 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解决scheme。

用法

 import javax.inject.Inject; import javax.inject.Named; @Named public class Foo { private Bar bar; @Inject public Foo(Bar bar) { this.bar = bar; } } 
  1. 部分依赖:可以使用setter注入来注入,但是它不可能被构造函数使用。 假设一个类有3个属性,有3个参数构造函数和setter方法。 在这种情况下,如果只想传递一个属性的信息,则只能通过setter方法。
  2. 覆盖: Setter注入覆盖构造函数注入。 如果我们同时使用构造函数和setter注入,IOC容器将使用setter注入。
  3. 变化:我们可以很容易地通过setter注入来改变这个值。 它不会像构造函数一样创build一个新的bean实例。 所以setter注入比构造注入更灵活

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

举个例子吧。

如果你想实例化一个类,你总是用它的构造函数来做。 所以,如果你正在使用基于构造函数的注入,实例化类的唯一方法是通过构造函数。 如果你通过构造函数传递依赖关系,那么显然它是一个强制性的依赖关系。

另一方面,如果你在POJO类中有一个setter方法,你可以使用setter方法为你的类variables设置或不设置值。 这是完全根据您的需要。 即它是可选的。 所以如果你通过一个类的setter方法传递依赖,它隐含地意味着它是一个可选的依赖。 希望这是明确的!

构造函数注入是在没有相关类的情况下不能运行的情况下使用的。

当类没有依赖类时可以使用属性注入。

作为一个具体的例子,考虑一个依靠IService来完成工作的ServiceRepository。 由于ServiceRepository在没有IService的情况下不能有效运行,因此通过构造函数注入是有意义的。

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

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

这个例子可能有助于:

控制器类:

 @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(); } 

红色class级:

 @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"); } } 

黑色class级:

 @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"); } } 

用于创buildBean的configuration类:

 @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); } } 

运行应用程序并点击URL:GET 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 

通过使用构造器注入,您以容器不可知的方式声明对依赖关系的要求

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

setter注入策略中,我们相信IoC容器,它将首先创buildbean,但是在使用setter方法使用bean之前会立即进行注入。 注射是根据你的configuration完成的。 如果你错过了指定要在configuration中注入的bean,注入将不会被这些bean所完成,并且当它将被使用时,你的依赖bean将不会相应地运行!

但是在构造器注入策略中,容器在构造bean的时候强加(或者必须强加)来正确地提供依赖关系。 这被称为“容器不可知的方式” ,因为我们需要在创buildbean的时候提供依赖关系,因此独立于任何IoC容器提供依赖关系的可视性。

编辑:

问题1:如何防止容器通过构造函数创buildnull值而不是丢失bean?

你没有select真的错过任何<constructor-arg> (在Spring的情况下),因为你是由IoC容器强加的,以提供所需的所有构造函数参数来匹配提供的创buildbean的构造函数。 如果你有意地在你的<constructor-arg>提供null 。 那么没有什么IoC容器可以做或者需要做它!

用例子? 这是一个简单的:

 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依赖性。