Spring @Autowire on Properties vs Constructor

GSU*_*bit 111 spring constructor dependency-injection autowired

因为我一直在使用Spring,如果我要编写一个具有依赖项的服务,我会执行以下操作:

@Component
public class SomeService {
     @Autowired private SomeOtherService someOtherService;
}
Run Code Online (Sandbox Code Playgroud)

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

@Component
public class SomeService {
    private final SomeOtherService someOtherService;

    @Autowired
    public SomeService(SomeOtherService someOtherService){
        this.someOtherService = someOtherService;
    }
}
Run Code Online (Sandbox Code Playgroud)

我理解这两种方法都有效.但是使用选项B有一些优势吗?对我来说,它在类和单元测试中创建了更多代码.(必须编写构造函数而不能使用@InjectMocks)

有什么我想念的吗?除了在单元测试中添加代码之外,还有其他任何自动装配的构造函数吗?这是一种更优先的依赖注入方式吗?

JB *_*zet 153

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

  • 清楚地确定了依赖关系.在任何其他情况下测试或实例化对象时都无法忘记一个(比如在config类中显式创建bean实例)
  • 依赖关系可以是最终的,这有助于提高健壮性和线程安全性
  • 您不需要反射来设置依赖项.InjectMocks仍然可用,但不是必需的.您可以自己创建模拟并通过简单地调用构造函数来注入它们

有关 更详细的文章,请参阅此博客文章,其中一位是Spring贡献者,Olivier Gierke.

  • 因此,让我们深入研究一下,假设您还有一些其他属性,例如 @Value("some.prop") 私有 String 属性;你也会把它放到构造函数中吗?看起来好像你最终会得到很长的完全参数化的构造函数。这本身并不坏,只是更多的代码。我绝对感谢您的评论! (4认同)
  • 是的,你也可以把它放在构造函数中.正如链接文章所说,当构造函数开始有太多参数时,通常表明你应该将类拆分为较小的类,而责任较少,依赖性较小. (4认同)
  • 字段上的@NisargPatil Autowired 使用反射直接设置字段,无需通过任何设置器。setter 上的 Autowired 调用 setter。 (3认同)
  • 您可以根据需要拥有任意数量的构造函数,但是只有其中一个可以用Autowired注释,并且将由Spring调用。 (2认同)

dev*_*per 32

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

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

除了在单元测试中添加代码之外,还有其他任何自动装配的构造函数吗?这是一种更优先的依赖注入方式吗?

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

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

sti*_*ger 24

请注意,从Spring 4.3 开始,您的构造函数甚至不需要 @Autowired,因此您可以用 Java 风格编写代码,而不是绑定到 Spring 的注释。您的代码段如下所示:

@Component
public class SomeService {
    private final SomeOtherService someOtherService;

    public SomeService(SomeOtherService someOtherService){
        this.someOtherService = someOtherService;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @el Mowgli - Spring 扫描您的类以查找与您的类字段匹配的构造函数。在这里查找详细信息:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-constructor-injection (5认同)
  • 您可以使用 Lombok 的注释:`@RequiredArgsConstructor` 来代替显式构造函数。 (3认同)
  • @stinger 并不是真的非常相关,但我认为很多项目都同时使用 lombok 和 Spring。在我们公司,我们有多个项目,我们同时使用这两种方法,这使得开发变得轻而易举。您无需编写构造函数(并可能更新它),只需使用“@RequiredArgsConstructor”并将任何注入的服务/组件标记为“final”。Lombok 将创建构造函数,Spring 将注入它。 (3认同)
  • 有趣,真的很好奇 spring 在这种情况下如何注入依赖项? (2认同)

Dan*_*ník 11

很高兴知道

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

@RestController
public class NiceController {

    private final DataRepository repository;

    public NiceController(ChapterRepository repository) {
        this.repository = repository;
    }
}
Run Code Online (Sandbox Code Playgroud)

... Spring Data Repository 注入示例。

  • 很高兴知道,但删除注释并不能提高可读性。 (5认同)

Nat*_*ber 8

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


Dou*_*e T 6

实际上,根据我的经验,第二种选择更好。无需@Autowired. 事实上,创建与框架耦合不太紧密的代码 (和 Spring 一样好)是更明智的做法。您需要尽可能多地尝试采用延迟决策方法的代码。那是尽可能多的pojo,如此之多,以至于框架可以轻松更换。所以我建议你创建一个单独的配置文件并在那里定义你的 bean,如下所示:

SomeService.java文件中:

public class SomeService {
    private final SomeOtherService someOtherService;

    public SomeService(SomeOtherService someOtherService){
        this.someOtherService = someOtherService;
    }
}
Run Code Online (Sandbox Code Playgroud)

ServiceConfig.java文件中:

@Config
public class ServiceConfig {
    @Bean
    public SomeService someService(SomeOtherService someOtherService){
        return new SomeService(someOtherService);
    }
}
Run Code Online (Sandbox Code Playgroud)

事实上,如果你想深入了解它,使用Field Injection ( @Autowired)会出现线程安全问题(除其他外),这显然取决于项目的大小。查看此内容以了解有关Autowiring优点和缺点的更多信息。实际上,关键人实际上建议您使用构造函数注入而不是字段注入


Swa*_*ick 5

Autowiredconstructors 提供了一个钩子,用于在将自定义代码注册到 spring 容器中之前添加自定义代码。假设SomeService类扩展了另一个名为 name 的类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();
    }
}
Run Code Online (Sandbox Code Playgroud)