一、前言

  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. 【第九篇】- Git 标签之Spring Cloud直播商城 b2b2c电子商务技术总结

    Git 标签 如果你达到一个重要的阶段,并希望永远记住那个特别的提交快照,你可以使用 git tag 给它打上标签. 比如说,我们想为我们的 xxx 项目发布一个"1.0"版本. ...

  2. 借助AWR报告分析解决oracleCPU过高的问题

    原文地址:http://www.cnblogs.com/crystal-guoguo/p/4213458.html 简介:在oracle数据库中,有两个非常实用的自带监控工具EM(Enterprise ...

  3. HDU - 2544最短路 (dijkstra算法)

    HDU - 2544最短路 Description 在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt.但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以 ...

  4. 洛谷P1803——凌乱的yyy(贪心)

    题目描述 现在各大oj上有n个比赛,每个比赛的开始.结束的时间点是知道的. yyy认为,参加越多的比赛,noip就能考的越好(假的) 所以,他想知道他最多能参加几个比赛. 由于yyy是蒟蒻,如果要参加 ...

  5. 迷你商城后台管理系统---------stage3项目部署测试汇总

    系统测试 在项目部署到云服务器之前,已通过本机启动springboot程序,访问localhost:8080,输入登陆的账户等一系列操作测试:功能测试.健壮性测试,系统已满足用户规定的需求. 系统部署 ...

  6. RestFul的认识与详解

    RestFul :是一种软件架构风格,设计风格,而不是标准.提供了一组设计原则和约束条件. 简单概述: REST -- REpresentational State Transfer 直接翻译:表现层 ...

  7. PHP中国际化的字符串比较对象

    在 PHP 中,国际化的功能非常丰富,包括很多我们可能都不知道的东西其实都非常有用,比如说今天要介绍的这一系列的字符排序和比较的功能. 排序 正常来说,如果我们对数组中的字符进行排序,按照的是字符的 ...

  8. html 随笔-水平控件不对齐的解决办法

    分别在左右两个控件的css代码中加上 vertical-align:top. 便可对齐:(推荐使用,因为这样可以避免脱标流). 来源: https://www.jianshu.com/p/f00d51 ...

  9. ☕【Java技术指南】「JPA编程专题」让你不再对JPA技术中的“持久化型注解”感到陌生了!

    JPA的介绍分析 Java持久化API (JPA) 显著简化了Java Bean的持久性并提供了一个对象关系映射方法,该方法使您可以采用声明方式定义如何通过一种标准的可移植方式,将Java 对象映射到 ...

  10. python BeautifulSoup html解析

    * BeautifulSoup 的.find(), .findAll() 函数原型 findAll(tag, attributes, recursive, text, limit, keywords) ...