【问题标题】:Spring @Autowire on Properties vs ConstructorSpring @Autowire on Properties vs Constructor
【发布时间】:2017-03-29 22:26:48
【问题描述】:

所以由于我一直在使用 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)

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

【问题讨论】:

标签: spring dependency-injection constructor autowired


【解决方案1】:

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

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

请参阅 this blog post 以获取更详细的文章,作者是 Spring 贡献者之一,Olivier Gierke

【讨论】:

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

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

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

除了添加代码之外,自动装配的构造函数还有什么作用吗? 到单元测试?这是一种更优选的依赖方式吗 注射?

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

【讨论】:

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

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

@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/…
  • ... 这似乎意味着构造函数注入成为默认的注入机制。您仍然需要显式传递所有依赖项的构造函数
【解决方案4】:

很高兴知道

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

@RestController
public class NiceController {

    private final DataRepository repository;

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

... Spring Data Repository 注入示例。

【讨论】:

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

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

【讨论】:

    【解决方案6】:

    实际上,根据我的经验,第二种选择更好。无需@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 的信息。实际上,关键人物实际上建议您使用 Constructor injection 而不是 Field Injection

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

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

        【讨论】:

        • 为什么不呢?我们不能将 bean 属性标记为 final?
        【解决方案9】:

        在少数情况下@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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-07-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-05-16
          • 1970-01-01
          相关资源
          最近更新 更多