因此,由于我一直在使用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)

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


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

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

请参阅这篇由Spring贡献者之一Olivier Gierke撰写的更详细的文章。

我用简单的话来解释:

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

除了添加代码,自动连接构造函数还有其他功能吗 单元测试?这是一种更受欢迎的依赖方式吗 注射吗?

选项(B)是首选方法,因为它不允许在没有实际解决SomeOtherService依赖关系的情况下创建SomeService对象。

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。事实上,更明智的做法是创建与框架耦合不太紧密的代码(就像Spring一样)。您希望代码尽可能采用延迟决策方法。这是尽可能多的pojo,以至于框架可以很容易地交换出来。 所以我建议你创建一个单独的Config文件,并在那里定义你的bean,如下所示:

在somesservice .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;
    }
}

and

@Service
public class DepartmentService {
    private final EmployeeService employeeService;

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

Then Spring Bean Factory will throw circular dependency exception. This won't happen if you use @Autowired annotation in both beans. And this is understandable: the constructor injection happens at very early stage of Spring Bean initialization, in createBeanInstance method of Bean Factory, while @Autowired-based injection happens way later, on post processing stage and is done by AutowiredAnnotationBeanPostProcessor. Circular dependency is quite common in complex Spring Context application, and it needs not to be just two beans referring one another, it could a complex chain of several beans.

另一个@Autowired非常有用的用例是自我注入。

@Service
public class EmployeeService {
    
    @Autowired
    private EmployeeService self;

}

从同一个bean中调用被通知的方法可能需要这样做。这里和这里也讨论了自注入。

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

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

有一种方法可以使用Lombok中的@ requeiredargsconstructor注释通过构造函数注入依赖项

@RequiredArgsConstructor
@Service
 class A {
     private final B b // needs to be declared final to be injected
}

这样就不需要指定构造函数