Mi1k7ea

Good Good Study


Spring基础篇之DI(依赖注入)

阅读量

0x00 参考

本笔记直接参考或引自如下链接文章:

https://www.w3cschool.cn/wkspring/

http://c.biancheng.net/spring/

0x01 Spring DI(依赖注入)

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

依赖注入(Dependency Injection,DI)和控制反转含义相同,它们是从两个角度描述的同一个概念。

当某个Java实例需要另一个Java实例时,传统的方法是由调用者创建被调用者的实例(例如,使用new关键字获得被调用者实例),而使用Spring框架后,被调用者的实例不再由调用者创建,而是由Spring容器创建,这称为控制反转。Spring容器在创建被调用者的实例时,会自动将调用者需要的对象实例注入给调用者,这样,调用者通过Spring容器获得被调用者实例,这称为依赖注入。

依赖注入主要有两种实现方式,分别是属性setter方法注入和构造方法注入:

  • 属性setter方法注入:指IoC容器使用setter方法注入被依赖的实例。通过调用无参构造器或无参static工厂方法实例化Bean后,调用该Bean的setter方法,即可实现基于setter方法的DI。
  • 构造方法注入:指IoC容器使用构造方法注入被依赖的实例。基于构造器的DI通过调用带参数的构造方法实现,每个参数代表一个依赖。

0x02 基于setter方法的依赖注入

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

Demo

TextEditor.java,spellChecker属性拥有setter方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.mi1k7ea;

public class TextEditor {
private SpellChecker spellChecker;
// a setter method to inject the dependency.
public void setSpellChecker(SpellChecker spellChecker) {
System.out.println("Inside setSpellChecker." );
this.spellChecker = spellChecker;
}
// a getter method to return spellChecker
public SpellChecker getSpellChecker() {
return spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}

SpellChecker.java:

1
2
3
4
5
6
7
8
9
10
package com.mi1k7ea;

public class SpellChecker {
public SpellChecker(){
System.out.println("Inside SpellChecker constructor." );
}
public void checkSpelling() {
System.out.println("Inside checkSpelling." );
}
}

MainApp.java:

1
2
3
4
5
6
7
8
9
10
11
12
package com.mi1k7ea;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
TextEditor te = (TextEditor) context.getBean("textEditor");
te.spellCheck();
}
}

Beans.xml,该文件有基于setter方法的依赖注入的配置,即添加property子标签,这是由于是要把一个引用传递给一个对象,因此使用ref属性(如果要直接传递一个值则使用value属性):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Definition for textEditor bean -->
<bean id="textEditor" class="com.mi1k7ea.TextEditor">
<property name="spellChecker" ref="spellChecker"/>
</bean>

<!-- Definition for spellChecker bean -->
<bean id="spellChecker" class="com.mi1k7ea.SpellChecker" />

</beans>

运行输出,可以看到在初始化Bean后容器就会调用Bean对象属性的setter方法进行属性值的依赖注入:

使用p-namespace实现XML配置

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>

<bean name="jane" class="com.example.Person">
<property name="name" value="John Doe"/>
</bean>

</beans>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="john-classic" class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
</bean>

<bean name="jane" class="com.example.Person"
p:name="John Doe"/>
</bean>

</beans>

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

0x03 基于构造函数的依赖注入

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

Demo

SpellChecker.java和MainApp.java不变。

TextEditor.java ,拥有一个带参数的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
package com.mi1k7ea;

public class TextEditor {
private SpellChecker spellChecker;
public TextEditor(SpellChecker spellChecker) {
System.out.println("Inside TextEditor constructor." );
this.spellChecker = spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}

Beans.xml,基于构造函数的依赖注入是在bean标签内添加constructor-arg子标签,其中ref属性值表示Bean的引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Definition for textEditor bean -->
<bean id="textEditor" class="com.mi1k7ea.TextEditor">
<constructor-arg ref="spellChecker"/>
</bean>

<!-- Definition for spellChecker bean -->
<bean id="spellChecker" class="com.mi1k7ea.SpellChecker" />

</beans>

运行输出,可以看到实例化Bean对象之后,容器会调用Bean对象的构造函数实现依赖注入:

构造函数参数解析

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

1
2
3
4
5
6
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}

下述配置文件工作顺利:

1
2
3
4
5
6
7
8
9
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>

<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>

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

1
2
3
4
5
6
package x.y;
public class Foo {
public Foo(int year, String name) {
// ...
}
}

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

1
2
3
4
5
6
7
8
<beans>

<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="2001"/>
<constructor-arg type="java.lang.String" value="Zara"/>
</bean>

</beans>

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

1
2
3
4
5
6
7
8
<beans>

<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="2001"/>
<constructor-arg index="1" value="Zara"/>
</bean>

</beans>

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

0x04 内部Bean注入

Inner Beans是在其他Bean的范围内定义的Bean。因此在bean元素内的bean元素被称为内部bean,通过这种方式也能实现依赖注入,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="outerBean" class="...">
<property name="target">
<bean id="innerBean" class="..."/>
</property>
</bean>

</beans>

Demo

SpellChecker.java和MainApp.java不变。

TextEditor.java ,添加spellChecker属性getter方法和setter方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.mi1k7ea;

public class TextEditor {
private SpellChecker spellChecker;
// a setter method to inject the dependency.
public void setSpellChecker(SpellChecker spellChecker) {
System.out.println("Inside setSpellChecker." );
this.spellChecker = spellChecker;
}
// a getter method to return spellChecker
public SpellChecker getSpellChecker() {
return spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}

Beans.xml,定义了一个内部bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Definition for textEditor bean using inner bean -->
<bean id="textEditor" class="com.mi1k7ea.TextEditor">
<property name="spellChecker">
<bean id="spellChecker" class="com.mi1k7ea.SpellChecker"/>
</property>
</bean>

</beans>

运行输出,由于spellChecker是textEditor的内部bean,因此会调用到setter方法实现依赖注入:

0x05 集合注入

为了可以传递多个值如Java Collection类型的List、Set、Map和Properties,Spring提供了四种类型的集合的配置元素,如下:

元素 描述
<list> 它有助于连线,如注入一列值,允许重复。
<set> 它有助于连线一组值,但不能重复。
<map> 它可以用来注入名称-值对的集合,其中名称和值可以是任何类型。
<props> 它可以用来注入名称-值对的集合,其中名称和值都是字符串类型。

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

两种情况:

  • 直接传递集合中的值;
  • 传递一个Bean的引用作为集合的元素;

Demo

JavaCollection.java,分别拥有List、Set、Map和Properties四个类型的属性及其setter和getter方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.mi1k7ea;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class JavaCollection {
List addressList;
Set addressSet;
Map addressMap;
Properties addressProp;
// a setter method to set List
public void setAddressList(List addressList) {
this.addressList = addressList;
}
// prints and returns all the elements of the list.
public List getAddressList() {
System.out.println("List Elements :" + addressList);
return addressList;
}
// a setter method to set Set
public void setAddressSet(Set addressSet) {
this.addressSet = addressSet;
}
// prints and returns all the elements of the Set.
public Set getAddressSet() {
System.out.println("Set Elements :" + addressSet);
return addressSet;
}
// a setter method to set Map
public void setAddressMap(Map addressMap) {
this.addressMap = addressMap;
}
// prints and returns all the elements of the Map.
public Map getAddressMap() {
System.out.println("Map Elements :" + addressMap);
return addressMap;
}
// a setter method to set Property
public void setAddressProp(Properties addressProp) {
this.addressProp = addressProp;
}
// prints and returns all the elements of the Property.
public Properties getAddressProp() {
System.out.println("Property Elements :" + addressProp);
return addressProp;
}
}

MainApp.java,获取id为javaCollection的Bean类对象,并调用其所有属性的getter方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.mi1k7ea;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
JavaCollection jc=(JavaCollection)context.getBean("javaCollection");
jc.getAddressList();
jc.getAddressSet();
jc.getAddressMap();
jc.getAddressProp();
}
}

Beans.xml,在bean标签内定义了4个property子标签即使用基于setter方法实现依赖注入,其中分别使用list、set、map、props等子标签来直接传递多个值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Definition for javaCollection -->
<bean id="javaCollection" class="com.mi1k7ea.JavaCollection">

<!-- results in a setAddressList(java.util.List) call -->
<property name="addressList">
<list>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>USA</value>
</list>
</property>

<!-- results in a setAddressSet(java.util.Set) call -->
<property name="addressSet">
<set>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>USA</value>
</set>
</property>

<!-- results in a setAddressMap(java.util.Map) call -->
<property name="addressMap">
<map>
<entry key="1" value="INDIA"/>
<entry key="2" value="Pakistan"/>
<entry key="3" value="USA"/>
<entry key="4" value="USA"/>
</map>
</property>

<!-- results in a setAddressProp(java.util.Properties) call -->
<property name="addressProp">
<props>
<prop key="one">INDIA</prop>
<prop key="two">Pakistan</prop>
<prop key="three">USA</prop>
<prop key="four">USA</prop>
</props>
</property>

</bean>

</beans>

运行输出:

注入Bean引用

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Bean Definition to handle references and values -->
<bean id="..." class="...">

<!-- Passing bean reference for java.util.List -->
<property name="addressList">
<list>
<ref bean="address1"/>
<ref bean="address2"/>
<value>Pakistan</value>
</list>
</property>

<!-- Passing bean reference for java.util.Set -->
<property name="addressSet">
<set>
<ref bean="address1"/>
<ref bean="address2"/>
<value>Pakistan</value>
</set>
</property>

<!-- Passing bean reference for java.util.Map -->
<property name="addressMap">
<map>
<entry key="one" value="INDIA"/>
<entry key ="two" value-ref="address1"/>
<entry key ="three" value-ref="address2"/>
</map>
</property>

</bean>

</beans>

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

注入null和空字符串的值

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

1
2
3
<bean id="..." class="exampleBean">
<property name="email" value=""/>
</bean>

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

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

1
2
3
<bean id="..." class="exampleBean">
<property name="email"><null/></property>
</bean>

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


Copyright © Mi1k7ea | 本站总访问量