Spring-依赖注入

本文最后更新于:2022年1月29日 晚上

Spring-依赖注入

控制反转(IoC)与依赖注入(DI)

控制反转

IoC是Inversion of Control的缩写,译为”控制反转”。

在面向对象的传统编程方式中,获取一个实例对象的方式是通过使用new关键字主动创建对象,而Spring中的IoC方式对象的生命周期由Spring框架提供的IoC容器来管理,应用程序从IoC容器中获取自己所需要的实例对象,控制权由应用程序转到了IoC容器

IoC理论上是借助于”第三方”实现具有依赖关系对象之间的解耦,即把各个对象类封装之后,通过IoC容器来关联这些对象类。这样对象与对象之间就通过IoC容器进行联系,而对象与对象之间没有什么直接联系。

应用程序在没有引入IoC容器之前,对象A依赖对象B(A的属性中有B的对象引用),那么A对象在实例化或者运行到某一点的时候,自己必须主动创建B对象,或者使用已经创建好的B对象,其中无论是创建还是使用已经创建的B对象,控制权都在应用程序自身。

如果应用程序引入IoC容器,对象A和对象B便失去了直接的联系,应用程序不需要主动去new一个对象B来供A使用,当A需要对象B时,IoC容器就会创建一个对象B注入(依赖注入)到A需要的地方去。由此,A获取依赖对象B的过程,由主动创建变成了被动获取,将创建对象的任务交给了IoC容器管理,控制权颠倒过来了,这就是所谓的控制反转

依赖注入

DI是Dependency Inject的缩写,译为”依赖注入”。

所谓”依赖注入”,就是IoC容器在运行期间,动态地将某种依赖关系注入到对象之中。

例如对象A有一个属性为对象B的引用,A的方法中需要调用B的方法,此时A不需要自己去创建对象B,而是IoC容器在创建对象A时,便通过依赖关系,创建了对象B,并注入到了A的需要的位置。

事实上,依赖注入(DI)和控制反转(IoC)是对同一事情的不同描述,只是他们描述的角度不同。

依赖注入是从应用程序的角度描述,即应用程序依赖容器创建并注入它所需的外部资源;而控制反转是从容器的角度描述,即容器获得控制权,控制对象的创建,控制权由应用程序转到容器,由容器反向地向应用程序中注入应用程序所需的外部资源。这里所说的外部资源,可以使外部实例对象,也可以是外部文件对象等。

Spring实现依赖注入

基于Setter()函数的依赖注入

假如有一个user对象,存在一个内部属性为data对象,则user对象依赖于data对象,此时需要在user对象中实现Setter()函数设置data的值,然后通过配置xml来实现注入。

Data对象:

1
2
3
4
5
public class Data {
public void show(){
System.out.println("show data");
}
}

User对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class User {

Data userData;

public void setUserData(Data userData) {
this.userData = userData;
}

public void show(){
//user调用data的show()方法
this.userData.show();
}
}

Test测试:

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
user.show();
}
}

如果只在xml文件中添加两个类对应的Bean的话:

1
2
<bean id="data" class="Pojo.Data"> </bean>
<bean id="user" class="Pojo.User"> </bean>

运行test之后,会出现空指针异常,因为获取到的user对象的属性userData并没有指向Data的实例对象,为空指针,调用show()方法时会出现空指针异常。

image-20220126233326729

下面将data注入user中:

1
2
3
4
<bean id="data" class="Pojo.Data"> </bean>
<bean id="user" class="Pojo.User">
<property name="userData" ref="data"> </property>
</bean>
  • property属性:

<bean>元素的子元素,用于调用 Bean 实例中的 setter 方法来属性赋值,从而完成依赖注入。该元素的 name 属性用于指定 Bean 实例中相应的属性名

注意,propety标签中的name的值是对应User类中的setUserData()方法中set后面的部分,且将开头的大写改为小写,而ref的值data,指的是xml文件中的bean的id是data,即上面定义的data的bean

再次运行:

image-20220126233244712

成功运行,说明此时User内部的userData属性已经被赋予了一个实例化的Data对象。

如果存在多个属性,只需要在User类中实现多个Setter函数,并在xml文件中配置多个property标签即可

User类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class User {

Data userData;
String name;


public void show(){
//user调用data的show()方法
System.out.println(this.userData);
this.userData.show();
System.out.println(name);
}

public void setUserData(Data userData) {
this.userData = userData;
}

public void setName(String name) {
this.name = name;
}
}

xml文件:

1
2
3
4
5
<bean id="data" class="Pojo.Data" scope="prototype"> </bean>
<bean id="user" class="Pojo.User">
<property name="userData" ref="data"> </property>
<property name="name" value="zhangsan"> </property>
</bean>

运行结果:

image-20220126235623190

基于构造函数的依赖注入

基于构造函数的依赖注入,实际上就是通过构造函数,将创建的实例对象注入另一个对象,在User对象中实现构造函数,构造函数为有参构造,参数为一个Data对象的引用,将该参数引用赋给User的成员属性userData。

Data对象与上面一致。

User对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class User {

Data userData;

public User(Data userData) {
this.userData = userData;
}

public void show(){
//user调用data的show()方法
this.userData.show();
}
}

xml文件:

1
2
3
4
<bean id="data" class="Pojo.Data"> </bean>
<bean id="user" class="Pojo.User">
<constructor-arg name="userData" ref="data"> </constructor-arg>
</bean>
  • constructor-arg属性:<bean>元素的子元素,可以使用此元素传入构造参数进行实例化。

注意constructor-arg标签中,name的值与user中有参构造器传入的参数名相同,ref依然是指xml文件中的bean的id

运行结果:

image-20220126234523779

如果存在多个属性,只需要改写构造器,并在xml中使用多个constructor-arg标签即可。

User类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class User {

Data userData;
Data userData2;

public User(Data userData) {
this.userData = userData;
}

public User(Data userData, Data userData2) {
this.userData = userData;
this.userData2 = userData2;
}

public void show(){
//user调用data的show()方法
System.out.println(this.userData);
this.userData.show();
System.out.println(this.userData2);
this.userData2.show();
}
}

xml文件:

1
2
3
4
5
<bean id="data" class="Pojo.Data" scope="prototype"> </bean>
<bean id="user" class="Pojo.User">
<constructor-arg name="userData" ref="data"> </constructor-arg>
<constructor-arg name="userData2" ref="data"> </constructor-arg>
</bean>

运行结果:

image-20220126235144485

可见,User中的两个Data对象都被注入。

引用注入与值注入

无论是基于Setter()函数的依赖注入,还是基于构造函数的依赖注入,如果注入是对象引用,那么在property或constructor-arg的子元素中,都是使用ref,ref的值为引用对象的bean的id。如果注入的是常量或值,比如”String”等,则在property或constructor-arg的子元素中,使用的是value,value的值为具体注入的值。例如:

1
2
3
4
5
<bean id="data" class="Pojo.Data" scope="prototype"> </bean>
<bean id="user" class="Pojo.User">
<property name="userData" ref="data"> </property>
<property name="name" value="zhangsan"> </property>
</bean>

集合注入

value可以配置基本数据类型,ref可以配置对象引用,但是每次处理的对象只有一个,如果想要传递多个值,如如 Java Collection 类型 List、Set、Map 和 Properties,应该怎么做呢。为了处理这种情况,Spring 提供了四种类型的集合的配置元素,如下所示:

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

测试如下:

User类:

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
public class User {

List<String> stringList;
Map<String,Data> dataMap;
Properties properties;


public void setStringList(List<String> stringList) {
this.stringList = stringList;
}

public void setDataMap(Map<String, Data> dataMap) {
this.dataMap = dataMap;
}

public void setProperties(Properties properties) {
this.properties = properties;
}


@Override
public String toString() {
return "User{" +
"stringList=" + stringList +
"\ndataMap=" + dataMap +
"\nproperties=" + properties +
'}';
}

public void show(){
System.out.println(this.toString());
}
}

xml文件,格式确实很迷糊:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<bean id="data" class="Pojo.Data" scope="prototype"> </bean>
<bean id="user" class="Pojo.User">
<property name="stringList">
<list>
<value>12345</value>
<value>11223</value>
<value>34324</value>
</list>
</property>
<property name="dataMap">
<map>
<entry key="123" value-ref="data"> </entry>
<entry key="456" value-ref="data"> </entry>
<entry key="789" value-ref="data"> </entry>
</map>
</property>
<property name="properties">
<props>
<prop key="one">111</prop>
<prop key="two">222</prop>
<prop key="three">333</prop>
</props>
</property>
</bean>

运行结果:

image-20220127002813791

引入其他配置文件

实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,并在一个配置文件中,通过import标签导入并加载其他的配置文件:

1
<import resource="applicationContext-xxx.xml"/>

本文作者: ziyikee
本文链接: https://ziyikee.fun/2022/01/26/Spring-%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!