Spring @Autowire属性vs构造函数

因此,由于我一直在使用Spring,如果我要编写一个有依赖关系的服务,我会这样做:

@Component
public class SomeService {
@Autowired private SomeOtherService someOtherService;
}

我现在遇到了使用另一种约定来实现相同目标的代码

@Component
public class SomeService {
private final SomeOtherService someOtherService;


@Autowired
public SomeService(SomeOtherService someOtherService){
this.someOtherService = someOtherService;
}
}

我知道这两种方法都有效。但是选择B有什么好处吗?对我来说,它在类和单元测试中创建了更多的代码。(必须写构造函数,不能使用@InjectMocks)

我遗漏了什么吗?除了将代码添加到单元测试之外,自动连接构造函数还有其他功能吗?这是一种更可取的依赖注入方式吗?

149479 次浏览

是的,选项B(称为构造函数注入)实际上推荐在字段注入之上,它有几个优点:

  • 依赖性被清楚地标识出来。在测试时,或者在任何其他情况下实例化对象时(比如在配置类中显式地创建bean实例),都不可能忘记一个对象。
  • 依赖关系可以是final的,这有助于健壮性和线程安全性
  • 您不需要反射来设置依赖项。InjectMocks仍然可用,但不是必需的。您可以自己创建mock,并通过简单地调用构造函数来注入它们

更详细的文章请参见这篇博文,由Spring贡献者之一奥利弗Gierke撰写。

我用简单的话来解释:

在选项(A),你允许任何人(在Spring容器外部/内部的不同类中)使用默认构造函数(如new SomeService())创建一个实例,这是不好的,因为你需要为你的SomeService创建SomeOtherService对象(作为依赖项)。

除了添加代码,自动连接构造函数还做什么 单元测试?这是一种更受欢迎的依赖方式吗 注射吗?< / p >

选项(B)是首选方法,因为它不允许创建SomeService对象而不实际解决SomeOtherService依赖。

Autowired构造函数提供了一个钩子,在将自定义代码注册到spring容器之前添加它。假设SomeService类扩展了另一个名为SuperSomeService的类,并且它有一个以名称作为参数的构造函数。在这种情况下,Autowired构造函数工作正常。同样,如果有其他成员要初始化,可以在将实例返回给spring容器之前在构造函数中进行初始化。

public class SuperSomeService {
private String name;
public SuperSomeService(String name) {
this.name = name;
}
}


@Component
public class SomeService extends SuperSomeService {
private final SomeOtherService someOtherService;
private Map<String, String> props = null;


@Autowired
public SomeService(SomeOtherService someOtherService){
SuperSomeService("SomeService")
this.someOtherService = someOtherService;
props = loadMap();
}
}

很高兴知道

如果只有一个构造函数调用,则不需要包含@Autowired注释。然后你可以使用这样的语句:

@RestController
public class NiceController {


private final DataRepository repository;


public NiceController(ChapterRepository repository) {
this.repository = repository;
}
}

... Spring数据存储库注入的例子。

事实上,根据我的经验,第二种选择更好。不需要@Autowired。事实上,更明智的做法是创建与框架耦合不太紧密的代码 (和春天一样好)。你希望代码尽可能地尝试采用延迟决策方法。这是尽可能多的pojo,以至于框架可以很容易地交换出来。 因此,我建议您创建一个单独的Config文件并在那里定义bean,如下所示:

SomeService.java文件中:

public class SomeService {
private final SomeOtherService someOtherService;


public SomeService(SomeOtherService someOtherService){
this.someOtherService = someOtherService;
}
}

ServiceConfig.java文件中:

@Config
public class ServiceConfig {
@Bean
public SomeService someService(SomeOtherService someOtherService){
return new SomeService(someOtherService);
}
}

事实上,如果你想深入了解它的技术,使用字段注入 (@Autowired)会出现线程安全问题(以及其他问题),这显然取决于项目的大小。看看这个,以了解更多关于自动装配的优点和缺点。实际上,关键的家伙实际上建议你使用构造函数注入而不是字段注入

请注意,由于Spring 4.3,你甚至不需要在你的构造函数上使用@Autowired,所以你可以用Java风格编写代码,而不是绑定到Spring的注释。 你的代码片段看起来像这样:

@Component
public class SomeService {
private final SomeOtherService someOtherService;


public SomeService(SomeOtherService someOtherService){
this.someOtherService = someOtherService;
}
}

我希望我不会因为表达我的观点而被降级,但对我来说,选项A更好地反映了Spring依赖注入的强大功能,而在选项B中,你将你的类与你的依赖事实上,如果不从构造函数传递对象的依赖项,就不能实例化对象耦合。依赖注入通过实现控制反转来避免这种情况,所以对我来说选项B没有任何意义。

很少有@Autowired更可取的情况。 其中之一是循环依赖。想象以下场景:

@Service
public class EmployeeService {
private final DepartmentService departmentService;


public EmployeeService(DepartmentService departmentService) {
this.departmentService = departmentService;
}
}

而且

@Service
public class DepartmentService {
private final EmployeeService employeeService;


public DepartmentService(EmployeeService employeeService) {
this.employeeService = employeeService;
}
}

则Spring Bean工厂将抛出循环依赖异常。如果在两个bean中都使用@Autowired注释,则不会发生这种情况。这是可以理解的:构造函数注入发生在Spring Bean初始化的早期阶段,在Bean工厂的createBeanInstance方法中,而基于__abc0的注入发生在较晚的后期处理阶段,由AutowiredAnnotationBeanPostProcessor完成。 循环依赖关系在复杂的Spring Context应用程序中非常常见,它不需要仅仅是两个bean相互引用,它可以是由几个bean组成的复杂链

另一个用例是自注入,其中@Autowired非常有用。

@Service
public class EmployeeService {
    

@Autowired
private EmployeeService self;


}

从同一个bean中调用建议方法可能需要这样做。自注入也讨论了在这里在这里

我更喜欢构造注入,只是因为我可以将我的依赖标记为final在使用属性注入注入属性时是不可能的。

你的依赖关系应该是最终的,即不被程序修改。