ChatGPT解决这个技术问题 Extra ChatGPT

Spring @Autowire on Properties vs Constructor

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

有什么我想念的吗?除了将代码添加到单元测试之外,自动装配的构造函数还有什么其他功能吗?这是进行依赖注入的更优选方式吗?


g
gfullam

是的,实际上建议使用选项 B(称为构造函数注入)而不是字段注入,并且具有以下几个优点:

清楚地确定了依赖关系。在测试或在任何其他情况下实例化对象时(例如在配置类中显式创建 bean 实例),没有办法忘记这一点

依赖关系可以是最终的,这有助于提高健壮性和线程安全性

您不需要反射来设置依赖项。 InjectMocks 仍然可用,但不是必需的。您可以自己创建模拟并通过简单地调用构造函数来注入它们

有关 Spring 贡献者之一 Olivier Gierke 的更详细文章,请参阅 this blog post


因此,让我们更深入地研究一下,假设您还有一些其他属性,例如 @Value("some.prop") private String property;您是否也将其放入构造函数中?看起来好像你最终会得到非常长的完全参数化的构造函数。这本身还不错,只是更多的代码。我非常感谢您的评论!
是的,你也可以把它放在构造函数中。正如链接文章所说,当构造函数开始有太多参数时,通常表明您应该将类拆分为具有更少责任和更少依赖关系的更小的类。
不知道我是怎么看到除了他的博客帖子之外的一切。感谢那!
如果您需要第二个构造函数,您需要使用@Autowire 还是可以使用两个构造函数?
您可以拥有任意数量的构造函数,但只有其中一个可以使用 Autowired 进行注释,并且会被 Spring 调用。
d
developer

我会用简单的话为你解释:

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

除了将代码添加到单元测试之外,自动装配的构造函数还有什么其他功能吗?这是进行依赖注入的更优选方式吗?

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


对于选项 B,这怎么不是“泄漏抽象”?如果我事先不知道某些服务的实际实现是什么,并且 Impl1 具有依赖关系 A/B/C 而 Impl2 具有 A/B/F/G,则构造方法将要求我预先知道依赖关系。但是,使用@autowire,当我尝试使用服务时,如果它在容器中注册了所有必要的依赖项,那么我很好,我不需要知道任何关于服务依赖项的信息。
@ChrisKnoll关键是要知道您的服务需要依赖项才能工作(尤其是测试!)。知道您的汽车需要钥匙才能启动并不是一个“泄漏的抽象”。您不需要知道 Key 的确切实现,但您确实需要知道您需要一个。此外,只要您的服务(SomeService)也被注释为(Service),那么您就不需要知道它的依赖关系是什么。在您的类(使用服务)中具有相同的自动装配注释,您可以调用 SomeService.perform() 而无需调用“new”关键字
s
stinger

请注意,从 Spring 4.3 开始,您甚至不需要在构造函数上使用 @Autowired,因此您可以使用 Java 样式编写代码,而不是绑定到 Spring 的注释。您的代码段看起来像这样:

@Component
public class SomeService {
    private final SomeOtherService someOtherService;

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

有趣,真的很好奇在这种情况下 spring 是如何注入依赖的?
@el Mowgli - Spring 扫描您的类以查找与您的类的字段匹配的构造函数。在此处查找详细信息:docs.spring.io/spring/docs/current/spring-framework-reference/…
...这似乎意味着构造函数注入成为默认的注入机制。您仍然需要显式传递所有依赖项的构造函数
您可以使用 Lombok 的注解:@RequiredArgsConstructor 而不是显式构造函数。
@MarcusViníciusVoltolim 您对 lombok 的评论与 Spring 自动装配技术有何关系?
D
Daniel Perník

很高兴知道

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

@RestController
public class NiceController {

    private final DataRepository repository;

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

... Spring Data Repository 注入示例。


很高兴知道,但删除注释并不能大大提高可读性。
当已经有另一个注释将该类标记为 DI 系统的一部分时,我宁愿拥有这个而不是一百个冗余的 Autowired
D
Dougie T

实际上,根据我的经验,第二种选择更好。无需@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 的更多信息。实际上,关键人物实际上建议您使用 构造函数注入 而不是 字段注入


S
Salvatore Pannozzo Capodiferro

我希望我不会因为表达我的意见而被降级,但对我来说,选项 A 更好地反映了 Spring 依赖注入的强大功能,而在选项 B 中,您将您的类与您的依赖项耦合在一起,实际上您无法实例化一个对象而不通过它对构造函数的依赖。已经发明了依赖注入来通过实现控制反转来避免这种情况,所以对我来说选项B没有任何意义。


A
Atul Dwivedi

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

J
Java Developer

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

您的依赖项应该是最终的,即不被程序修改。


为什么不?我们可能不会将 bean 属性标记为最终属性?
i
igor.zh

@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 方法。 herehere 还讨论了自注入。