本文最后更新于: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对象:
| public class Data { public void show(){ System.out.println("show data"); } }
|
User对象:
| public class User {
Data userData;
public void setUserData(Data userData) { this.userData = userData; }
public void show(){ this.userData.show(); } }
|
Test测试:
| 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的话:
| <bean id="data" class="Pojo.Data"> </bean> <bean id="user" class="Pojo.User"> </bean>
|
运行test之后,会出现空指针异常,因为获取到的user对象的属性userData并没有指向Data的实例对象,为空指针,调用show()方法时会出现空指针异常。
下面将data注入user中:
| <bean id="data" class="Pojo.Data"> </bean> <bean id="user" class="Pojo.User"> <property name="userData" ref="data"> </property> </bean>
|
<bean>
元素的子元素,用于调用 Bean 实例中的 setter 方法来属性赋值,从而完成依赖注入。该元素的 name 属性用于指定 Bean 实例中相应的属性名
注意,propety标签中的name的值是对应User类中的setUserData()方法中set后面的部分,且将开头的大写改为小写,而ref的值data,指的是xml文件中的bean的id是data,即上面定义的data的bean
再次运行:
成功运行,说明此时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(){ 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文件:
| <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>
|
运行结果:
基于构造函数的依赖注入
基于构造函数的依赖注入,实际上就是通过构造函数,将创建的实例对象注入另一个对象,在User对象中实现构造函数,构造函数为有参构造,参数为一个Data对象的引用,将该参数引用赋给User的成员属性userData。
Data对象与上面一致。
User对象:
| public class User {
Data userData;
public User(Data userData) { this.userData = userData; }
public void show(){ this.userData.show(); } }
|
xml文件:
| <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
运行结果:
如果存在多个属性,只需要改写构造器,并在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(){ System.out.println(this.userData); this.userData.show(); System.out.println(this.userData2); this.userData2.show(); } }
|
xml文件:
| <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>
|
运行结果:
可见,User中的两个Data对象都被注入。
引用注入与值注入
无论是基于Setter()函数的依赖注入,还是基于构造函数的依赖注入,如果注入是对象引用,那么在property或constructor-arg的子元素中,都是使用ref,ref的值为引用对象的bean的id。如果注入的是常量或值,比如”String”等,则在property或constructor-arg的子元素中,使用的是value,value的值为具体注入的值。例如:
| <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>
|
运行结果:
引入其他配置文件
实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,并在一个配置文件中,通过import标签导入并加载其他的配置文件:
| <import resource="applicationContext-xxx.xml"/>
|