Spring注解依赖注入的三种方式的优缺点以及优先选择
当我们在使用依赖注入的时候,通常有三种方式:
1.通过构造器来注入;
2.通过setter方法来注入;
3.通过filed变量来注入;
那么他们有什么区别吗?应该选择哪种方式更好?
代码示例:
Constructor
1 private DependencyA dependencyA; 2 private DependencyB dependencyB; 3 private DependencyC dependencyC; 4 5 @Autowired 6 public DI(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC) { 7 this.dependencyA = dependencyA; 8 this.dependencyB = dependencyB; 9 this.dependencyC = dependencyC; 10 }
Setter
1 private DependencyA dependencyA; 2 private DependencyB dependencyB; 3 private DependencyC dependencyC; 4 5 @Autowired 6 public void setDependencyA(DependencyA dependencyA) { 7 this.dependencyA = dependencyA; 8 } 9 10 @Autowired 11 public void setDependencyB(DependencyB dependencyB) { 12 this.dependencyB = dependencyB; 13 } 14 15 @Autowired 16 public void setDependencyC(DependencyC dependencyC) { 17 this.dependencyC = dependencyC; 18 }
Field
1 @Autowired 2 private DependencyA dependencyA; 3 4 @Autowired 5 private DependencyB dependencyB; 6 7 @Autowired 8 private DependencyC dependencyC;
三种方式的区别小结:
1.基于constructor的注入,会固定依赖注入的顺序;该方式不允许我们创建bean对象之间的循环依赖关系,这种限制其实是一种利用构造器来注入的益处 - 当你甚至没有注意到使用setter注入的时候,Spring能解决循环依赖的问题;
2.基于setter的注入,只有当对象是需要被注入的时候它才会帮助我们注入依赖,而不是在初始化的时候就注入;另一方面如果你使用基于constructor注入,CGLIB不能创建一个代理,迫使你使用基于接口的代理或虚拟的无参数构造函数。
3.相信很多同学都选择使用直接在成员变量上写上注解来注入,正如我们所见,这种方式看起来非常好,精短,可读性高,不需要多余的代码,也方便维护;
缺点:
1.当我们利用constructor来注入的时候,比较明显的一个缺点就是:假如我们需要注入的对象特别多的时候,我们的构造器就会显得非常的冗余、不好看,非常影响美观和可读性,维护起来也较为困难;
2.当我们选择setter方法来注入的时候,我们不能将对象设为final的;
3.当我们在field变量上来实现注入的时候
a.这样不符合JavaBean的规范,而且很有可能引起空指针;
b.同时也不能将对象标为final的;
c.类与DI容器高度耦合,我们不能在外部使用它;
d.类不通过反射不能被实例化(例如单元测试中),你需要用DI容器去实例化它,这更像集成测试;
... etc.
来自Spring官方文档的建议:
在Spring 3.x 中,Spring团队建议我们使用setter来注入:
大致是说大量的构造器参数会显得非常笨重,尤其是当属性是可选的时候。setter方法可以使类的对象在后来重新配置或者重新注入。提供所有的依赖意味着对象总是返回一个完全初始化状态的client客户端(调用)。缺点是对象变得不那么适合重新配置和重新注入。
而在Spring 4.x 中,Spring团队不再建议我们使用setter来注入,改为了constructor:
Spring团队通常建议使用构造器来注入,因为它允许一个应用程序组件实现为不可变对象,并确保所需的依赖项不是空。此外构造器注入组件总是返回一个完全初始化状态的client客户端(调用)。附注,大量的构造函数参数是一个糟糕的代码习惯,看起来也很坏,这意味着类可能有太多的责任,应该被重构,以更好地解决适当的关注点分离。
setter方法只应该主要的用在可以在类中指定合理的默认值的可选的依赖关系。否则,用到依赖的所有地方都应该进行非空检查。setter注入的一个好处是,setter方法使类的对象可以在之后重新配置或者重新注入。
(以上是本人的渣渣英语翻译结合有道得来。。大佬看到请轻喷)
接下来插播一条Spring 4.3 的新特征:
在Spring 4.3 以后,如果我们的类中只有单个构造函数,那么Spring就会实现一个隐式的自动注入,上代码:
之前:
1 @Service 2 public class FooService { 3 4 private final FooRepository repository; 5 6 @Autowired 7 public FooService(FooRepository repository) { 8 this.repository = repository 9 } 10 }
在Spring 4.3 之后:
1 @Service 2 public class FooService { 3 4 private final FooRepository repository; 5 6 public FooService(FooRepository repository) { 7 this.repository = repository 8 } 9 }
如我们所见,我去掉了构造器上的@Autowired注解,经测试后发现,程序能正常运行,repository的依赖也被成功注入了,当时感觉就很amazing。。有兴趣的同学可以试试~
总结:
1.强制性的依赖性或者当目标不可变时,使用构造函数注入(应该说尽量都使用构造器来注入)
2.可选或多变的依赖使用setter注入(建议可以使用构造器结合setter的方式来注入)
3.在大多数的情况下避免field域注入(感觉大多数同学可能会有异议,毕竟这个方式写起来非常简便,但是它的弊端确实远大于这些优点)
4.Spring 4.3+ 的同学可以试一试构造器的隐式注入,采用此方式注入后,使得我们的代码更优雅,更独立,减少了对Spring的依赖性。
ps: 转载请标注出处谢谢。