3.Spring 依赖注入

1.Spring 依赖注入

Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系。

依赖注入

每个基于应用程序的 java 都有几个对象,这些对象一起工作来呈现出终端用户所看到的工作的应用程序。当编写一个复杂的 Java 应用程序时,应用程序类应该尽可能独立于其他 Java 类来增加这些类重用的可能性,并且在做单元测试时,测试独立于其他类的独立性。依赖注入(或有时称为布线)有助于把这些类粘合在一起,同时保持他们独立。

假设你有一个包含文本编辑器组件的应用程序,并且你想要提供拼写检查。标准代码看起来是这样的:

publicclassTextEditor{privateSpellChecker spellChecker;publicTextEditor(){      spellChecker =newSpellChecker();  }}

在这里我们所做的就是创建一个 TextEditor 和 SpellChecker 之间的依赖关系。在控制反转的场景中,我们反而会做这样的事情:

publicclassTextEditor{privateSpellChecker spellChecker;publicTextEditor(SpellChecker spellChecker){this.spellChecker = spellChecker;  }}

在这里,TextEditor 不应该担心 SpellChecker 的实现。SpellChecker 将会独立实现,并且在 TextEditor 实例化的时候将提供给 TextEditor,整个过程是由 Spring 框架的控制。

在这里,我们已经从 TextEditor 中删除了全面控制,并且把它保存到其他地方(即 XML 配置文件),且依赖关系(即 SpellChecker 类)通过类构造函数被注入到 TextEditor 类中。因此,控制流通过依赖注入(DI)已经“反转”,因为你已经有效地委托依赖关系到一些外部系统。

依赖注入的第二种方法是通过 TextEditor 类的 Setter 方法,我们将创建 SpellChecker 实例,该实例将被用于调用 setter 方法来初始化 TextEditor 的属性。

因此,DI 主要有两种变体和下面的两个子章将结合实例涵盖它们:

序号依赖注入类型 & 描述

1Constructor-based dependency injection

当容器调用带有多个参数的构造函数类时,实现基于构造函数的 DI,每个代表在其他类中的一个依赖关系。

2Setter-based dependency injection

基于 setter 方法的 DI 是通过在调用无参数的构造函数或无参数的静态工厂方法实例化 bean 之后容器调用 beans 的 setter 方法来实现的。

你可以混合这两种方法,基于构造函数和基于 setter 方法的 DI,然而使用有强制性依存关系的构造函数和有可选依赖关系的 sette r是一个好的做法。

代码是 DI 原理的清洗机,当对象与它们的依赖关系被提供时,解耦效果更明显。对象不查找它的依赖关系,也不知道依赖关系的位置或类,而这一切都由 Spring 框架控制的。


2.Spring 基于构造函数的依赖注入

Spring 基于构造函数的依赖注入

当容器调用带有一组参数的类构造函数时,基于构造函数的 DI 就完成了,其中每个参数代表一个对其他类的依赖。

接下来,我们将通过示例来理解 Spring 基于构造函数的依赖注入。

示例:

下面的例子显示了一个类 TextEditor,只能用构造函数注入来实现依赖注入。

让我们用 Eclipse IDE 适当地工作,并按照以下步骤创建一个 Spring 应用程序。

步骤描述

1创建一个名为 SpringExample 的项目,并在创建的项目中的 src 文件夹下创建包 com.tutorialspoint 。

2使用 Add External JARs 选项添加必需的 Spring 库,解释见 Spring Hello World Example chapter.

3在 com.tutorialspoint 包下创建 Java类 TextEditorSpellChecker 和 MainApp

4在 src 文件夹下创建 Beans 的配置文件 Beans.xml 。

5最后一步是创建所有 Java 文件和 Bean 配置文件的内容并按照如下所示的方法运行应用程序。

这是 TextEditor.java 文件的内容:

packagecom.tutorialspoint;publicclassTextEditor{privateSpellChecker spellChecker;publicTextEditor(SpellChecker spellChecker){      System.out.println("Inside TextEditor constructor.");this.spellChecker = spellChecker;  }publicvoidspellCheck(){      spellChecker.checkSpelling();  }}

下面是另一个依赖类文件 SpellChecker.java 的内容:

packagecom.tutorialspoint;publicclassSpellChecker{publicSpellChecker(){      System.out.println("Inside SpellChecker constructor.");  }publicvoidcheckSpelling(){      System.out.println("Inside checkSpelling.");  } }

以下是 MainApp.java 文件的内容:

packagecom.tutorialspoint;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;publicclassMainApp{publicstaticvoidmain(String[] args){      ApplicationContext context =newClassPathXmlApplicationContext("Beans.xml");      TextEditor te = (TextEditor) context.getBean("textEditor");      te.spellCheck();  }}

下面是配置文件 Beans.xml 的内容,它有基于构造函数注入的配置:

    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">                 

当你完成了创建源和 bean 配置文件后,让我们开始运行应用程序。如果你的应用程序运行顺利的话,那么将会输出下述所示消息:

Inside SpellChecker constructor.

Inside TextEditor constructor.

Inside checkSpelling.

构造函数参数解析:

如果存在不止一个参数时,当把参数传递给构造函数时,可能会存在歧义。要解决这个问题,那么构造函数的参数在 bean 定义中的顺序就是把这些参数提供给适当的构造函数的顺序就可以了。考虑下面的类:

packagex.y;publicclassFoo{publicFoo(Bar bar, Baz baz){// ...}}

下述配置文件工作顺利:

让我们再检查一下我们传递给构造函数不同类型的位置。考虑下面的类:

packagex.y;publicclassFoo{publicFoo(intyear, String name){// ...}}

如果你使用 type 属性显式的指定了构造函数参数的类型,容器也可以使用与简单类型匹配的类型。例如:

最后并且也是最好的传递构造函数参数的方式,使用 index 属性来显式的指定构造函数参数的索引。下面是基于索引为 0 的例子,如下所示:

最后,如果你想要向一个对象传递一个引用,你需要使用 标签的 ref 属性,如果你想要直接传递值,那么你应该使用如上所示的 value 属性。



3.Spring 基于设值函数的依赖注入

Spring 基于设值函数的依赖注入

当容器调用一个无参的构造函数或一个无参的静态 factory 方法来初始化你的 bean 后,通过容器在你的 bean 上调用设值函数,基于设值函数的 DI 就完成了。

示例:

下述例子显示了一个类 TextEditor,它只能使用纯粹的基于设值函数的注入来实现依赖注入。

让我们用 Eclipse IDE 适当地工作,并按照以下步骤创建一个 Spring 应用程序。

步骤描述

1创建一个名为 SpringExample 的项目,并在创建的项目中的 src 文件夹下创建包 com.tutorialspoint 。

2使用 Add External JARs 选项添加必需的 Spring 库,解释见 Spring Hello World Example chapter.

3在 com.tutorialspoint 包下创建 Java类 TextEditorSpellChecker 和 MainApp

4在 src 文件夹下创建 Beans 的配置文件 Beans.xml 。

5最后一步是创建所有 Java 文件和 Bean 配置文件的内容并按照如下所示的方法运行应用程序。

下面是 TextEditor.java 文件的内容:

packagecom.tutorialspoint;publicclassTextEditor{privateSpellChecker spellChecker;// a setter method to inject the dependency.publicvoidsetSpellChecker(SpellChecker spellChecker){      System.out.println("Inside setSpellChecker.");this.spellChecker = spellChecker;  }// a getter method to return spellCheckerpublicSpellCheckergetSpellChecker(){returnspellChecker;  }publicvoidspellCheck(){      spellChecker.checkSpelling();  }}

在这里,你需要检查设值函数方法的名称转换。要设置一个变量 spellChecker,我们使用 setSpellChecker() 方法,该方法与 Java POJO 类非常相似。让我们创建另一个依赖类文件 SpellChecker.java 的内容:

packagecom.tutorialspoint;publicclassSpellChecker{publicSpellChecker(){      System.out.println("Inside SpellChecker constructor.");  }publicvoidcheckSpelling(){      System.out.println("Inside checkSpelling.");  }  }

以下是 MainApp.java 文件的内容:

packagecom.tutorialspoint;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;publicclassMainApp{publicstaticvoidmain(String[] args){      ApplicationContext context =newClassPathXmlApplicationContext("Beans.xml");      TextEditor te = (TextEditor) context.getBean("textEditor");      te.spellCheck();  }}

下面是配置文件 Beans.xml 的内容,该文件有基于设值函数注入的配置:

    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">                 

你应该注意定义在基于构造函数注入和基于设值函数注入中的 Beans.xml 文件的区别。唯一的区别就是在基于构造函数注入中,我们使用的是〈bean〉标签中的〈constructor-arg〉元素,而在基于设值函数的注入中,我们使用的是〈bean〉标签中的〈property〉元素。

第二个你需要注意的点是,如果你要把一个引用传递给一个对象,那么你需要使用 标签的 ref 属性,而如果你要直接传递一个值,那么你应该使用 value 属性。

当你完成了创建源和 bean 配置文件后,让我们开始运行应用程序。如果你的应用程序运行顺利的话,那么将会输出下述所示消息:

Inside SpellChecker constructor.

Inside setSpellChecker.

Inside checkSpelling.

使用 p-namespace 实现 XML 配置:

如果你有许多的设值函数方法,那么在 XML 配置文件中使用 p-namespace 是非常方便的。让我们查看一下区别:

以带有 标签的标准 XML 配置文件为例:

    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">                         

上述 XML 配置文件可以使用 p-namespace 以一种更简洁的方式重写,如下所示:

    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">       

在这里,你不应该区别指定原始值和带有 p-namespace 的对象引用。-ref 部分表明这不是一个直接的值,而是对另一个 bean 的引用。


4.Spring 注入内部 Beans

注入内部 Beans

正如你所知道的 Java 内部类是在其他类的范围内被定义的,同理,inner beans 是在其他 bean 的范围内定义的 bean。因此在 或 元素内 元素被称为内部bean,如下所示。

    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">                       

例子

我们在适当的位置使用 Eclipse IDE,然后按照下面的步骤来创建一个 Spring 应用程序:

步骤描述

1创建一个名称为 SpringExample 的项目,并且在创建项目的 src 文件夹中创建一个包 com.tutorialspoint 。

2使用 Add External JARs 选项,添加所需的 Spring 库,解释见 Spring Hello World Example 章节。 option as explained in the chapter.

3在 com.tutorialspoint 包中创建Java类TextEditorSpellChecker 和 MainApp

4在 src 文件夹中创建 Beans 配置文件 Beans.xml

5最后一步是创建的所有Java文件和Bean配置文件的内容,并运行应用程序,解释如下所示。

这里是 TextEditor.java 文件的内容:

packagecom.tutorialspoint;publicclassTextEditor{privateSpellChecker spellChecker;// a setter method to inject the dependency.publicvoidsetSpellChecker(SpellChecker spellChecker){      System.out.println("Inside setSpellChecker.");this.spellChecker = spellChecker;  }// a getter method to return spellCheckerpublicSpellCheckergetSpellChecker(){returnspellChecker;  }publicvoidspellCheck(){      spellChecker.checkSpelling();  }}

下面是另一个依赖的类文件 SpellChecker.java 内容:

packagecom.tutorialspoint;publicclassSpellChecker{publicSpellChecker(){      System.out.println("Inside SpellChecker constructor.");  }publicvoidcheckSpelling(){      System.out.println("Inside checkSpelling.");  }  }

下面是 MainApp.java 文件的内容:

packagecom.tutorialspoint;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;publicclassMainApp{publicstaticvoidmain(String[] args){      ApplicationContext context =newClassPathXmlApplicationContext("Beans.xml");      TextEditor te = (TextEditor) context.getBean("textEditor");      te.spellCheck();  }}

下面是使用内部 bean 为基于 setter 注入进行配置的配置文件 Beans.xml 文件:

    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">                         

一旦你创建源代码和 bean 配置文件完成后,我们就可以运行该应用程序。如果你的应用程序一切都正常,将输出以下信息:

Inside SpellChecker constructor.

Inside setSpellChecker.

Inside checkSpelling.




5.Spring 注入集合

注入集合

你已经看到了如何使用 value 属性来配置基本数据类型和在你的 bean 配置文件中使用<property>标签的 ref 属性来配置对象引用。这两种情况下处理奇异值传递给一个 bean。

现在如果你想传递多个值,如 Java Collection 类型 List、Set、Map 和 Properties,应该怎么做呢。为了处理这种情况,Spring 提供了四种类型的集合的配置元素,如下所示:

元素描述

<list>它有助于连线,如注入一列值,允许重复。

<set>它有助于连线一组值,但不能重复。

<map>它可以用来注入名称-值对的集合,其中名称和值可以是任何类型。

<props>它可以用来注入名称-值对的集合,其中名称和值都是字符串类型。

你可以使用<list>或<set>来连接任何 java.util.Collection 的实现或数组。

你会遇到两种情况(a)传递集合中直接的值(b)传递一个 bean 的引用作为集合的元素。

例子

我们在适当的位置使用 Eclipse IDE,然后按照下面的步骤来创建一个 Spring 应用程序:

步骤描述

1创建一个名称为 SpringExample 的项目,并且在创建项目的 src 文件夹中创建一个包 com.tutorialspoint 。

2使用 Add External JARs 选项,添加所需的 Spring 库,解释见 Spring Hello World Example 章节。 option as explained in the chapter.

3在 com.tutorialspoint 包中创建Java类TextEditorSpellChecker 和 MainApp

4在 src 文件夹中创建 Beans 配置文件 Beans.xml

5最后一步是创建的所有Java文件和Bean配置文件的内容,并运行应用程序,解释如下所示。

这里是 JavaCollection.java 文件的内容:

packagecom.tutorialspoint;importjava.util.*;publicclassJavaCollection{  List addressList;  Set  addressSet;  Map  addressMap;  Properties addressProp;// a setter method to set ListpublicvoidsetAddressList(List addressList){this.addressList = addressList;  }// prints and returns all the elements of the list.publicListgetAddressList(){      System.out.println("List Elements :"+ addressList);returnaddressList;  }// a setter method to set SetpublicvoidsetAddressSet(Set addressSet){this.addressSet = addressSet;  }// prints and returns all the elements of the Set.publicSetgetAddressSet(){      System.out.println("Set Elements :"+ addressSet);returnaddressSet;  }// a setter method to set MappublicvoidsetAddressMap(Map addressMap){this.addressMap = addressMap;  }// prints and returns all the elements of the Map.publicMapgetAddressMap(){      System.out.println("Map Elements :"+ addressMap);returnaddressMap;  }// a setter method to set PropertypublicvoidsetAddressProp(Properties addressProp){this.addressProp = addressProp;  }// prints and returns all the elements of the Property.publicPropertiesgetAddressProp(){      System.out.println("Property Elements :"+ addressProp);returnaddressProp;  }}

下面是 MainApp.java 文件的内容:

packagecom.tutorialspoint;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;publicclassMainApp{publicstaticvoidmain(String[] args){      ApplicationContext context =newClassPathXmlApplicationContext("Beans.xml");      JavaCollection jc=(JavaCollection)context.getBean("javaCollection");      jc.getAddressList();      jc.getAddressSet();      jc.getAddressMap();      jc.getAddressProp();  }}

下面是配置所有类型的集合的配置文件 Beans.xml 文件:

<?xml version="1.0"encoding="UTF-8"?>

    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"><!-- Definition for javaCollection --><!-- results in a setAddressList(java.util.List) call -->INDIAPakistanUSAUSA<!-- results in a setAddressSet(java.util.Set) call -->INDIAPakistanUSAUSA<!-- results in a setAddressMap(java.util.Map) call --><!-- results in a setAddressProp(java.util.Properties) call -->INDIAPakistanUSAUSA

一旦你创建源代码和 bean 配置文件完成后,我们就可以运行该应用程序。你应该注意这里不需要配置文件。如果你的应用程序一切都正常,将输出以下信息:

List Elements :[INDIA, Pakistan, USA, USA]

Set Elements :[INDIA, Pakistan, USA]

Map Elements :{1=INDIA, 2=Pakistan, 3=USA, 4=USA}

Property Elements :{two=Pakistan, one=INDIA, three=USA, four=USA}

注入 Bean 引用

下面的 Bean 定义将帮助你理解如何注入 bean 的引用作为集合的元素。甚至你可以将引用和值混合在一起,如下所示:

<?xml version="1.0"encoding="UTF-8"?>

    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"><!-- Bean Definition to handle references and values --><!-- Passing bean reference  for java.util.List -->Pakistan<!-- Passing bean reference  for java.util.Set -->Pakistan<!-- Passing bean reference  for java.util.Map -->

为了使用上面的 bean 定义,你需要定义 setter 方法,它们应该也能够是用这种方式来处理引用。

注入 null 和空字符串的值

如果你需要传递一个空字符串作为值,那么你可以传递它,如下所示:

前面的例子相当于 Java 代码:exampleBean.setEmail("")。

如果你需要传递一个 NULL 值,那么你可以传递它,如下所示:

前面的例子相当于 Java 代码:exampleBean.setEmail(null)。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343