前言
很多时候,我们在使用IDEA时,会发现注解@Autowired依赖注入 对于字段注入是不被推荐的,那么我们今天就来聊一下,作为Spring的特性之一的依赖注入,是如何优雅的实现的。
依赖注入,分为,字段注入、setter方法注入、构造器注入三种方式,那么我们就来聊一下,其中三者的好处,以及如何更好地选择使用。
理论上来说可以使用任何一个,它们的结果完全相同。但是,基于易用性,可读性,编码标准或更好的编码实践,有一定的差异。
字段注入
@Componentpublic class MyClass { @Autowired private DogsController controller; @Autowired private DogsService service; @Autowired private DogsDao dao; @Autowired private ApplicationProperties properties; //... // Business methods //}
Setter方法注入
@Componentpublic class MyClass { private DogsController controller; private DogsService service; private DogsDao dao; private ApplicationProperties properties; @Autowired public void setController(DogsController controller) { this.controller = controller; } @Autowired public void setService(DogsService service) { this.service = service; } @Autowired public void setDao(DogsDao dao) { this.dao = dao; } @Autowired public void setProperties(ApplicationProperties properties) { this.properties = properties; } //... // Business methods //}
构造方法注入
@Componentpublic class MyClass { private DogsController controller; private DogsService service; private DogsDao dao; private ApplicationProperties properties; @Autowired public MyClass(DogsController controller, DogsService service, DogsDao dao, ApplicationProperties properties) { this.controller = controller; this.service = service; this.dao = dao; this.properties = properties; } //... // Business methods //}
以上具有所有三种类型的依赖项注入的代码参考。让我们在面向对象设计原则的一些重要方面进行评估。
可读性
可读性是对一个软件程序的理解程度的判断。开发人员花费30%的时间编写软件,并花费70%的时间维护软件。可读性提高了软件的可维护性。
对所有三种方式的应用的可读性进行度量可见:
字段注入:最好的。更少的代码。重点是业务逻辑。
构造器注入:更好。构造函数在视觉上与方法分开。
Setter注入:最糟糕的。添加了4个实例方法。造成极差的可读性。
不变性
用软件编程的术语来说,如果对象在创建后不能被修改,则称其为不可变。不变性是良好的面向对象编程的重要原则。不变性为类带来了线程安全性,状态安全性和可读性。
如果我们从不变性的角度看上面的代码示例:
构造函数注入:支持不变性。
setter注入:可变。
字段注入:可变。
状态安全性
对象很可能由使用者或基础框架实例化。对象本身应为实例化器提供规则或指导,以便他们将以正确的状态调用对象。如果对象没有规定这样的状态安全性,则有可能将对象实例化为不完整或不正确的状态。
注意:以上所有示例都是状态安全的,因为Spring正在解析它们的依赖项,并且Spring将正确初始化所有字段,这些字段是@Autowired的一部分。但是某些使用者可能会使用新的关键字实例化您的对象。我们应该研究Spring框架之外的状态安全性。
如果我们从状态安全性的角度看上面的代码示例:
构造函数注入:状态安全。该对象被实例化为完整状态或完全不被实例化。
setter注入:消费者使用无参数构造函数。而且可能会错过调用设置器之一或两次以不同的值调用相同的设置器(复制粘贴错误)
字段注入:使用者使用无参数构造函数。没有有效的方法来设置对象的状态。唯一的选择是使用反射设置私有字段。
对象大量待注入字段
让我们考虑一个对象具有6、7或更多字段的情况
字段注入:看起来仍然更好,而且可读性强。依赖项被隔离在一个地方。
@Componentpublic class MyClass { @Autowired private Service1 service1; @Autowired private Service2 service2; @Autowired private Service3 service3; @Autowired private Service4 service4; @Autowired private Service5 service5; @Autowired private Service6 service6; @Autowired private Service7 service7; @Autowired private Service8 service7; //... // Business methods //}
构造器注入:简而言之,丑的不行 !!构造函数注入确实看起来很丑。对于使用者来说也不容易使用。
@Componentpublic class MyClass { private Service1 service1; private Service2 service2; private Service3 service3; private Service4 service4; private Service5 service5; private Service6 service6; private Service7 service7; private Service8 service7; @Autowired public MyClass(Service1 service1, Service2 service2, Service3 service3, Service4 service4, Service5 service5, Service6 service6, Service7 service7, Service8 service71) { this.service1 = service1; this.service2 = service2; this.service3 = service3; this.service4 = service4; this.service5 = service5; this.service6 = service6; this.service7 = service7; this.service7 = service71; } //... // Business methods //}
Setter注入:不好。它添加了8个额外的实例方法,仅用于设置依赖项。
@Componentpublic class MyClass { private Service1 service1; private Service2 service2; private Service3 service3; private Service4 service4; private Service5 service5; private Service6 service6; private Service7 service7; private Service8 service7; @Autowired public void setService1(Service1 service1) { this.service1 = service1; } @Autowired public void setService2(Service2 service2) { this.service2 = service2; } @Autowired public void setService3(Service3 service3) { this.service3 = service3; } @Autowired public void setService4(Service4 service4) { this.service4 = service4; } @Autowired public void setService5(Service5 service5) { this.service5 = service5; } @Autowired public void setService6(Service6 service6) { this.service6 = service6; } @Autowired public void setService7(Service7 service7) { this.service7 = service7; } @Autowired public void setService7(Service8 service7) { this.service7 = service7; } //... // Business methods //}
对于大量依赖的情况下,无疑,字段注入是更该推荐的。但是,实际上对于我们程序的设计来说,不会出现太多的依赖。程序设计的原则之一是单一责任原则。
问题出现频率
基于大多数程序员开发的实际研究发现,使用字段注入,太依赖Spring容器,很多时候,会发生NullPointerException。而且,从设计来说,本身不应该过度的依赖容器,而应该保持独立。因此,字段注入在这种形势下,是不是被推荐的。
总结
根据上面的代码示例和我们的分析,很明显,基于构造的依赖注入在所有情况下始终表现得更好。构造函数注入是最佳选择。
注意:所有设计原则或更好的编码标准以及我们在此处讨论的内容都只是准则,而不是规则。