一、前言

  Spring文档严格只定义了两种类型的注入:构造函数注入和setter注入。但是,还有更多的方式来注入依赖项,例如字段注入,查找方法注入。下面主要是讲使用Spring框架时可能发生的类型。

二、构造函数注入(Constructor Injection)

  这是最简单和推荐的依赖项注入方式。一个依赖类有一个构造函数,所有的依赖都被设置了,它们将由Spring容器根据XML,Java或基于注释的配置来提供。

 1 package example;
2
3 import org.springframework.context.ApplicationContext;
4 import org.springframework.context.support.ClassPathXmlApplicationContext;
5
6 class Service1 {
7 void doSmth() {
8 System.out.println("Service1");
9 }
10 }
11
12 class Service2 {
13 void doSmth() {
14 System.out.println("Service2");
15 }
16 }
17
18 class DependentService {
19 private final Service1 service1;
20 private final Service2 service2;
21
22 public DependentService(Service1 service1, Service2 service2) {
23 this.service1 = service1;
24 this.service2 = service2;
25 }
26
27 void doSmth() {
28 service1.doSmth();
29 service2.doSmth();
30 }
31
32 }
33
34 public class Main {
35 public static void main(String[] args) {
36 ApplicationContext container = new ClassPathXmlApplicationContext("spring.xml");
37 ((DependentService) container.getBean("dependentService")).doSmth();
38 }
39 }

  这里有2个独立服务和1个从属服务。依赖关系仅在构造函数中设置,要运行它,我们将初始化容器,并为其提供spring.xml中包含的配置。

 1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="
5 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
6
7 <bean id="service1" class="example.Service1"/>
8 <bean id="service2" class="example.Service2"/>
9
10 <bean id="dependentService" class="example.DependentService">
11 <constructor-arg type="example.Service1" ref="service1"/>
12 <constructor-arg type="example.Service2" ref="service2"/>
13 </bean>
14
15 </beans>

  我们将所有3个服务声明为Spring bean,最后一个将前2个的引用为构造函数参数。但需要配置的东西比较多,是否需要编写这么多的配置?

  我们可以使用“自动装配”功能,让Spring猜测从我们这边进行的最小配置工作应注入到什么位置。为了帮助Spring定位我们的代码,让我们用注解@Service和构造函数标记我们的服务,在注解中使用@Autowired或@Inject进行注入。@Autowired属于Spring框架,@Inject属JSR-330批注集合。

 1 package example;
2
3 import org.springframework.beans.factory.annotation.Autowired;
4 import org.springframework.context.ApplicationContext;
5 import org.springframework.context.support.ClassPathXmlApplicationContext;
6 import org.springframework.stereotype.Service;
7
8 @Service
9 class Service1 {
10 void doSmth() {
11 System.out.println("Service1");
12 }
13 }
14
15 @Service
16 class Service2 {
17 void doSmth() {
18 System.out.println("Service2");
19 }
20 }
21
22 @Service
23 class DependentService {
24 private final Service1 service1;
25 private final Service2 service2;
26
27 @Autowired
28 public DependentService(Service1 service1, Service2 service2) {
29 this.service1 = service1;
30 this.service2 = service2;
31 }
32
33 void doSmth() {
34 service1.doSmth();
35 service2.doSmth();
36 }
37
38 }
39
40 public class Main {
41 public static void main(String[] args) {
42 ApplicationContext container = new ClassPathXmlApplicationContext("spring.xml");
43 ((DependentService) container.getBean("dependentService")).doSmth();
44 }
45 }

  通过注解定义bean需要在spring.xml中开启以下配置:

1 <beans xmlns="http://www.springframework.org/schema/beans"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xmlns:context="http://www.springframework.org/schema/context"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
5
6 <!-- 使用注解来构造IoC容器 -->
7 <context:component-scan base-package="example"/>
8 <beans/>

  Spring提供了甚至在纯XML方法中也可以使用自动装配的可能性。但是,强烈建议避免使用此功能。

  构造函数注入的优点:

  1. 构造的对象是不可变的,并以完全初始化的状态返回给客户端。

  2. 通过这种方法可以立即看到具有越来越多的依赖关系的问题。依赖越多,构造函数就越大。

  3. 可以与setter注入或字段注入结合使用,构造函数参数指示所需的依赖关系,其他(可选)。

  缺点:

  1. 以后无法更改对象的依赖关系-不灵活性。

  2. 具有循环依赖关系的机会更高,即所谓的“鸡与蛋”场景。

三、setter注入(Setter Injection)

  使用与之前示例相同的代码,简洁起见,构造函数注入不会有太多更改,因此仅更改的逻辑部分。

 1 class DependentService {
2 private Service1 service1;
3 private Service2 service2;
4
5 public void setService1(Service1 service1) {
6 this.service1 = service1;
7 }
8
9 public void setService2(Service2 service2) {
10 this.service2 = service2;
11 }
12
13 void doSmth() {
14 service1.doSmth();
15 service2.doSmth();
16 }
17
18 }

  正如注入类型的名称所暗示的那样,我们应该首先为依赖项创建setter。在这种情况下,不需要构造函数。让我们看一下XML配置中应该做什么。

 1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="
5     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
6
7 <bean id="service1" class="example.Service1"/>
8 <bean id="service2" class="example.Service2"/>
9
10 <bean id="dependentService" class="example.DependentService">
11 <property name="service1" ref="service1"/>
12 <property name="service2" ref="service2"/>
13 </bean>
14
15 </beans>

  除了XML配置,还有基于注解的版本。

 1 @Service
2 class DependentService {
3 private Service1 service1;
4 private Service2 service2;
5
6 @Autowired
7 public void setService1(Service1 service1) {
8 this.service1 = service1;
9 }
10
11 @Autowired
12 public void setService2(Service2 service2) {
13 this.service2 = service2;
14 }
15
16 void doSmth() {
17 service1.doSmth();
18 service2.doSmth();
19 }
20
21 }

  这种类型的注入需要有Setter的存在,就像构造函数注入需要构造函数一样。

  在描述构造函数注入的优点时,提到它可以轻松地与setter注入结合使用。

 1 @Service
2 class DependentService {
3 private final Service1 service1;
4 private Service2 service2;
5
6 @Autowired
7 public DependentService(Service1 service1) {
8 this.service1 = service1;
9 }
10
11 @Autowired
12 public void setService2(Service2 service2) {
13 this.service2 = service2;
14 }
15
16 void doSmth() {
17 service1.doSmth();
18 service2.doSmth();
19 }
20 }

  在示例中,service1对象是强制性依赖项(最终属性),在DependentService实例实例化期间仅设置一次。同时,service2是可选的,可以首先包含null,并且其值可以在创建后随时通过调用setter进行更改。用XML的方式,必须在bean声明中同时指定属性和builder-arg标记。

  优点

  1. 依赖关系解析或对象重新配置的灵活性,可以随时进行。另外,这种自由解决了构造函数注入的循环依赖问题。

  缺点

  1. null值检查是必需的,因为当前可能未设置依赖项。
  2. 由于可能会覆盖依赖项,因此与构造函数注入相比,潜在的错误倾向更大,安全性更低。

四、字段注入(Field Injection)

 1 @Service
2 class DependentService {
3 @Autowired
4 private Service1 service1;
5 @Autowired
6 private Service2 service2;
7
8 void doSmth() {
9 service1.doSmth();
10 service2.doSmth();
11 }
12 }

  这种注入方式只有在基于注解的方法中才有可能,因为它实际上并不是一种新的注入方式,在较为底层的实现机制中,Spring使用反射来设置这些值。

  优点

  1. 易于使用,无需构造器或设置器

  2. 可以轻松地与构造函数和(或)setter方法结合使用

  缺点

  1. 对对象实例化的控制较少。为了实例化测试的类的对象,需要配置Spring容器或模拟库,这取决于要编写的测试。

  2. 在你发现设计中出现问题之前,许多依赖项可能达到数十种。

  3. 依赖不是一成不变的,与setter注入相同。

五、查找方法注入(Lookup Method Injection)

  由于特殊的使用情况,该注入类型的使用频率比上述所有其他类型的使用频率低,即依赖项的注入具有较小的寿命。

  默认情况下,Spring中的所有bean均创建为singleton(单例),这意味着它们将在容器中创建一次,并且将在请求的任何位置注入相同的对象。但是,有时需要不同的策略,例如,每个方法调用都应从一个新对象完成。现在,假设将这个寿命短的对象注入到singleton对象中,Spring会在每次调用时自动刷新此依赖项吗?不,除非我们指出存在这种特殊的依赖类型,否则依赖仍将被视为单例。

  回到实践中,我们又有了3个服务,其中一个依赖于其他服务,service2是通常的对象,可以通过任何先前描述的依赖项注入技术(例如,setter注入)注入DependentService中。Service1的对象将有所不同,不能被注入一次,每次调用都应访问一个新实例—让我们创建一个提供该对象的方法,并让Spring知道。

 1 abstract class DependentService {
2 private Service2 service2;
3
4 public void setService2(Service2 service2) {
5 this.service2 = service2;
6 }
7
8 void doSmth() {
9 createService1().doSmth();
10 service2.doSmth();
11 }
12
13 protected abstract Service1 createService1();
14 }

  我们没有将Service1的对象声明为通常的依赖项,而是指定了Spring框架将覆盖该方法的方法,以便返回Service1类的最新实例。

  依赖的方法提供者不一定要抽象和保护,它可以是公共的,也可以包含实现,但是请记住,它将在Spring创建的子类中被覆盖。

  我们再次处于纯XML配置示例中,因此我们必须首先指出service1是一个寿命较短的对象,在Spring术语中,我们可以使用prototype范围(有多个实例),因为它小于单例。通过lookup-method标记,我们可以指示将注入依赖项的方法的名称。

 1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="
5 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
6
7 <bean id="service1" class="example.Service1" scope="prototype"/>
8 <bean id="service2" class="example.Service2"/>
9
10 <bean id="dependentService" class="example.DependentService">
11 <lookup-method name="createService1" bean="service1"/>
12 <property name="service2" ref="service2"/>
13 </bean>
14
15 </beans>

  也可以以基于注解的方式完成相同操作。

 1 @Service
2 @Scope(value = "prototype")
3 class Service1 {
4 void doSmth() {
5 System.out.println("Service1");
6 }
7 }
8
9 @Service
10 abstract class DependentService {
11 private Service2 service2;
12
13 @Autowired
14 public void setService2(Service2 service2) {
15 this.service2 = service2;
16 }
17
18 void doSmth() {
19 createService1().doSmth();
20 service2.doSmth();
21 }
22
23 @Lookup
24 protected abstract Service1 createService1();
25 }

  劣势与优势

  将这种类型的依赖注入与其他类型的依赖注入进行比较是不正确的,因为它具有完全不同的用法。

六、结论

  我们经历了由Spring框架实现的4种类型的依赖注入:

  1. 构造函数注入- 良好,可靠且不变的,通过构造函数之一进行注入。可以配置为:XML,XML +注释,Java,Java +注释。

  2. Setter注入- 更灵活,易变的对象,通过 Setter进行注入。可以配置为:XML,XML +注释,Java,Java +注释。

  3. 与IoC容器结合使用,可快速方便地进行字段注入。可以在XML + Annotations,Java + Annotations中进行配置。

  4. 查找方法注入——与其他方法完全不同,用于较小范围的注入依赖性。可以配置为:XML,XML +注释,Java,Java +注释。

参考:参考一参考二参考三参考四

Spring:所有依赖项注入的类型的更多相关文章

  1. 第 6 章 —— 依赖项注入(DI)容器 —— Ninject

    有些读者只想理解 MVC 框架所提供的特性,而不想介入开发理念与开发方法学.笔者不打算让你改变 —— 这属于个人取向,而且你知道交付优质项目需要的是什么. 建议你至少粗略第看一看本章的内容,以明白哪些 ...

  2. .Net核心依赖项注入:生命周期和最佳实践

    在讨论.Net的依赖注入(DI)之前,我们需要知道我们为什么需要使用依赖注入 依赖反转原理(DIP): DIP允许您将两个类解耦,否则它们会紧密耦合,这有助于提高可重用性和更好的可维护性 DIP介绍: ...

  3. Spring根据XML配置文件注入对象类型属性

    这里有dao.service和Servlet三个地方 通过配过文件xml生成对象,并注入对象类型的属性,降低耦合 dao文件代码: package com.swift; public class Da ...

  4. 在WPF中使用.NET Core 3.0依赖项注入和服务提供程序

    前言 我们都知道.NET Core提供了对依赖项注入的内置支持.我们通常在ASP.NET Core中使用它(从Startup.cs文件中的ConfigureServices方法开始),但是该功能不限于 ...

  5. Spring的IoC容器注入的类型

    Spring除了可以注入Bean实例外,还可以注入其他数据类型. 注入基本数据类型 xml配置文件中的init-method="init"属性是取得Bean实例之后,输入属性值后自 ...

  6. Spring核心技术(四)——Spring的依赖及其注入(续二)

    前面两篇文章描述了IoC容器中依赖的概念,包括依赖注入以及注入细节配置.本文将继续描述玩全部的依赖信息. 使用 depends-on 如果一个Bean是另一个Bean的依赖的话,通常来说这个Bean也 ...

  7. Spring核心技术(三)——Spring的依赖及其注入(续)

    本文将继续前文,针对依赖注入的细节进行描述 依赖注入细节 如前文所述,开发者可以通过定义Bean的依赖的来引用其他的Bean或者是一些值的,Spring基于XML的配置元数据通过支持一些子元素< ...

  8. Spring核心技术(二)——Spring的依赖及其注入

    本文将继续前文,描述Spring IoC中的依赖处理. 依赖 一般情况下企业应用不会只有一个对象(或者是Spring Bean).甚至最简单的应用都要多个对象来协同工作来让终端用户看到一个完整的应用的 ...

  9. Spring Bean依赖但注入(autowired或resource)时NullPointerException(xml和annotation混用的场景下)

    项目中同时使用了xml和annotation的方式管理Spring Bean 启动时候报NullPointerException,依赖注入失败! 参考: http://fly0wing.iteye.c ...

随机推荐

  1. 详解Java中==和equals()的区别

    众所周知,在 Java 编程中,程序员通常会使用==或equals()来简单的比较地址,内容是否相等.而这两者之间的使用区别,对于初学 Java 的同学来说可能会比较迷糊.我将根据下面的几段示例程序, ...

  2. File Inclusion(文件包含)

    File Inclusion,意思是文件包含(漏洞),是指当服务器开启allow_url_include选项时,就可以通过php的某些特性函数(include(),require()和include_ ...

  3. .NET 6 中的HTTP 3支持

    dotnet团队官方博客发布了一篇HTTP3的文章:HTTP/3 support in .NET 6:https://devblogs.microsoft.com/dotnet/http-3-supp ...

  4. 在FLASH中读写结构体

    在FLASH中读写结构体 注意事项 编程(写数据)地址要对齐 写数据时,我们要指定写入的地址,如果写入地址为非对齐,则会出现编程对齐错误. 比如遵循32位(4字节)地址对齐,你的地址只能是4的倍数.0 ...

  5. PHP中的那些魔术常量

    之前我们已经了解了一些常用的魔术方法,除了魔术方法外,PHP还提供一些魔术常量,相信大家在日常的工作中也都使用过,这里给大家做一个总结. 其实PHP还提供了很多常量但都依赖于各类扩展库,而有几个常量是 ...

  6. Jmeter系列(14)- Setup与tearDown线程组

    与普通线程组区别 #Setup线程组:在普通线程组执⾏前触发 #tearDown线程组:在普通线程组执⾏后触发 线程组属性配置详情完全⼀致 使⽤策略建议 #Setup 线程组 – 压测执⾏准备阶段,准 ...

  7. django 使用装饰器验证用户登陆

    使用装饰器验证用户登陆,需要使用@method_decorator 首先需引用,method_decorator,并定义一个闭包 from django.utils.decorators import ...

  8. JDK源码阅读(3):AbstractStringBuilder、StringBuffer、StringBuilder类阅读笔记

    AbstractStringBuilder abstract class AbstractStringBuilder implements Appendable, CharSequence{ ... ...

  9. 看动画学算法之:平衡二叉搜索树AVL Tree

    目录 简介 AVL的特性 AVL的构建 AVL的搜索 AVL的插入 AVL的删除 简介 平衡二叉搜索树是一种特殊的二叉搜索树.为什么会有平衡二叉搜索树呢? 考虑一下二叉搜索树的特殊情况,如果一个二叉搜 ...

  10. 初识Linux shell

    目录 初识Linux shell Linux 深入探究Linux内核 系统内存管理 交换空间 页面 换出 软件程序管理 Linux中的进程 Linux系统的运行级 硬件设备管理 插入设备驱动代码的方法 ...