因此,由于我一直在使用 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 实例),没有办法忘记这一点
依赖关系可以是最终的,这有助于提高健壮性和线程安全性
您不需要反射来设置依赖项。 InjectMocks 仍然可用,但不是必需的。您可以自己创建模拟并通过简单地调用构造函数来注入它们
有关 Spring 贡献者之一 Olivier Gierke 的更详细文章,请参阅 this blog post。
我会用简单的话为你解释:
在 Option(A) 中, 您允许任何人(在 Spring 容器外部/内部的不同类中)使用默认构造函数(如 new SomeService()
)创建实例,这不是您需要的{ 2} SomeService
的对象(作为依赖项)。
除了将代码添加到单元测试之外,自动装配的构造函数还有什么其他功能吗?这是进行依赖注入的更优选方式吗?
Option(B) 是首选方法,因为它不允许在不实际解决 SomeOtherService
依赖关系的情况下创建 SomeService
对象。
请注意,从 Spring 4.3 开始,您甚至不需要在构造函数上使用 @Autowired,因此您可以使用 Java 样式编写代码,而不是绑定到 Spring 的注释。您的代码段看起来像这样:
@Component
public class SomeService {
private final SomeOtherService someOtherService;
public SomeService(SomeOtherService someOtherService){
this.someOtherService = someOtherService;
}
}
@RequiredArgsConstructor
而不是显式构造函数。
很高兴知道
如果只有一个构造函数调用,则不需要包含 @Autowired 注释。然后你可以使用这样的东西:
@RestController
public class NiceController {
private final DataRepository repository;
public NiceController(ChapterRepository repository) {
this.repository = repository;
}
}
... Spring Data Repository 注入示例。
实际上,根据我的经验,第二种选择更好。无需@Autowired
。实际上,创建与框架耦合不太紧密的代码会更明智 (和 Spring 一样好)。您希望代码尽可能采用延迟决策方法。这是尽可能多的 pojo,以至于框架可以轻松更换。所以我建议你创建一个单独的配置文件并在那里定义你的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);
}
}
事实上,如果您想深入了解它,使用 Field Injection (@Autowired
) 会出现线程安全问题(除其他事项外),具体取决于项目明显。 Check this out 以了解有关 advantages and disadvantages of Autowiring 的更多信息。实际上,关键人物实际上建议您使用 构造函数注入 而不是 字段注入
我希望我不会因为表达我的意见而被降级,但对我来说,选项 A 更好地反映了 Spring 依赖注入的强大功能,而在选项 B 中,您将您的类与您的依赖项耦合在一起,实际上您无法实例化一个对象而不通过它对构造函数的依赖。已经发明了依赖注入来通过实现控制反转来避免这种情况,所以对我来说选项B没有任何意义。
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();
}
}
我更喜欢构造注入,因为我可以将我的依赖项标记为 final,这在使用属性注入注入属性时是不可能的。
您的依赖项应该是最终的,即不被程序修改。
@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 Factory 会抛出循环依赖异常。如果您在两个 bean 中都使用 @Autowired
注释,则不会发生这种情况。这是可以理解的:构造函数注入发生在 Spring Bean 初始化的早期阶段,在 Bean Factory 的 createBeanInstance
方法中,而基于 @Autowired
的注入发生在稍后的后期处理阶段,由 AutowiredAnnotationBeanPostProcessor
完成。循环依赖在复杂的 Spring Context 应用程序中很常见,它不必只是两个相互引用的 bean,它可以是多个 bean 的复杂链。
@Autowired
非常有用的另一个用例是自注入。
@Service
public class EmployeeService {
@Autowired
private EmployeeService self;
}
这可能需要在同一个 bean 中调用 advised 方法。 here 和 here 还讨论了自注入。
不定期副业成功案例分享