之前在引出spring时,就明确了如果要水平组装两个java对象的话,通过开发人员编程式组装是很不友好的。
所以,spring出现的目的就是解决了java对象的实例化问题,和java对象之间的组装问题。
我们把一个A对象需要依赖B对象去处理某些功能的场景,称之为A对象依赖B对象,也叫做向A对象中注入B对象。
我们很明确了,java对象之间的依赖就是我们所谓的委托机制。
一般而言,系统中的类和类之间都存在依赖关系,即一个类会委托另外一个类去处理某些功能。
这个时候我们就需要向目标类中注入被依赖的对象。
学习委托机制时,我们知道想让A对象持有B对象,总的来说有4种方式,而其中最常见的方式就是B对象作为A对象的成员变量,通过外部注入。即以成员变量的方式注入B对象。
而spring的DI依赖注入机制,实际上就是采用这种方式来注入依赖关系的。
这种以成员变量的方式注入B对象,一般有2种具体操作:
1. 外部直接通过构造方法向目标类注入成员变量,这要求目标类存在有参构造。
2. 外部直接通过setter方法向目标类注入成员变量,这要求目标类存在setter方法。
下面我们来详细介绍spring提供的DI!
1. 回顾依赖
1.1 什么是依赖关系?
当一个对象A对外提供的功能需要其他对象来完成的时候,我们就称对象A和其他对象之间存在依赖关系。
如下:
class DiService{
public void test(){
}
}
public class DiController {
private DiService diService;
public void test(){
this.diService.test();
}
}
DiController中的test方法需要调用DiService中的test方法,说明DiController依赖DiService,此时如果diService对象不存在,则DiController无法对外提供test功能。
那么我们在创建DiController对象时是如何向其中设置diService对象呢?
上面我们已经给出了答案,常见的就两种方式:
通过构造器设置依赖对象;
通过setter方法设置依赖对象。
1.2 通过构造器设置依赖对象
DiController中添加一个有参构造,如下:
public class DiController {
private DiService diService;
public DiController(DiService diService) {
this.diService = diService;
}
public void test() {
this.diService.test();
}
}
这样,外部在创建DiController对象时,就可以直接通过其提供的有参构造将diService对象给注入进去,然后DiController内部就可以使用diService对象了。
DiController diController = new DiController(new DiService());
1.3 通过set方法设置依赖对象
DiController中为diService添加一个setter方法,如下:
public class DiController {
private DiService diService;
public DiService getDiService() {
return diService;
}
public void setDiService(DiService diService) {
this.diService = diService;
}
public void test() {
this.diService.test();
}
}
这样,外部在创建DiController对象时,就可以直接通过其提供的setter方法将diService对象给注入进去。
DiController diController = new DiController();
diController.setDiService(new DiService());
2. 依赖注入
上面通过编程的方式向DiController中注入了diService对象,将被依赖的对象设置到依赖的对象中,spring容器本身对这种操作提供了支持。
这在spring中叫做依赖注入。
spring中依赖注入主要分为手动注入和自动注入,本文介绍手动注入,手动注入需要我们明确配置需要注入的对象。
刚才我们回顾了,将被依赖的对象注入到依赖方中,通常有2种方式:
通过构造方法的方式完成注入。
通过set属性的方式完成注入。
spring也是通过这两种方式完成依赖注入的。
3. spring完成依赖注入的方式-通过构造器注入
通过构造器完成注入时,构造器中的参数就是被依赖的对象,这从侧面证实了使用构造器注入依赖的话,构造器必须是有参构造。
构造器注入又分为3种注入方式,这3种方式我们在之前学习spring通过构造器反射实例化bean的时候学习过,分别是:
1. 根据构造器参数索引注入
2. 根据构造器参数类型注入
3. 根据构造器参数名称注入
3.1 根据构造器参数索引注入依赖
用法:
<bean id="diModel" class="com.zeh.main.pojo.DiModel">
<constructor-arg index="0" value="eric" />
<constructor-arg index="1" value="我是通过构造器的参数索引位置注入的" />
</bean>
constructor-arg标签用于让用户指定构造器参数。
index:构造器参数的索引位置,从0开始,指定0则表示构造器第1个参数,指定1则表示构造器第2个参数,以此类推。
value:指定的构造器参数的值,value只能用来给简单类型的参数设置值,value设置值时对应的属性类型只能为 string、byte、int、long、float、double、boolean、Byte、Long、Float、Double、枚举等,spring容器内部注入时会将value的值转换为对应的类型。
DiModel:
package com.zeh.main.pojo;
/**
* 功能描述
*
* @since 2021-05-10
*/
public class DiModel {
private String name;
private int age;
private String desc;
public DiModel() {
}
public DiModel(String name, String desc) {
this.name = name;
this.desc = desc;
}
public DiModel(String name, int age, String desc) {
this.name = name;
this.age = age;
this.desc = desc;
}
@Override
public String toString() {
return "DiModel{" + "name = '" + this.name + "',age = '" + this.age + "',desc = '" + this.desc + "'}";
}
}
注意上面bean里面的3个构造方法。
bean xml:
<!-- 通过构造器参数的索引注入 -->
<bean id="diModel" class="com.zeh.main.pojo.DiModel">
<constructor-arg index="0" value="eric" />
<constructor-arg index="1" value="我是通过构造器的参数索引位置注入的" />
</bean>
上面bean xml配置创建的DiModel相当于如下java代码:
DiModel diModel = new DiModel("eric", "我是通过构造器的参数索引位置注入的");
工具类IocUtil:
package com.zeh.main.util;
import org.springframework.context.support.ClassPathXmlApplicationContext;
// 功能描述 工具类,根据bean xml配置获取spring容器对象
public class IocUtil {
public static ClassPathXmlApplicationContext getIocContext(String beanXml) {
return new ClassPathXmlApplicationContext(beanXml);
}
}
测试:
package com.zeh.main.controller;
import com.zeh.main.util.IocUtil;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DiModelController {
public static void main(String[] args) {
String beanXml = "classpath:/spring/applicationContext.xml";
ClassPathXmlApplicationContext context = IocUtil.getIocContext(beanXml);
System.out.println(String.format("bean is %s", context.getBean("diModel")));
}
}
测试结果:
bean is DiModel{name = 'eric',age = '0',desc = '我是通过构造器的参数索引位置注入的'}
优缺点:
参数位置的注入对参数顺序有很强的依赖性,若构造方法的参数位置被人调整过,会导致注入出错。
不过一般情况下,不建议在代码中去修改构造方法,如果需要新增参数,可以新增一个构造方法来实现,这算是一种扩展,不会影响目前已有的功能。
3.2 根据构造器参数类型注入依赖
用法:
<!-- 通过构造器参数的类型注入 -->
<bean id="diModel" class="com.zeh.main.pojo.DiModel">
<constructor-arg type="参数类型" value="参数值" />
<constructor-arg type="参数类型" value="参数值" />
</bean>
constructor-arg标签用于让用户指定构造器参数。
type:构造方法参数的完整类型,比如 java.lang.String,int,double
value:构造方法参数的值,value只能用来给简单类型设置值
bean xml:
<!-- 通过构造器参数的类型注入 -->
<bean id="diModel" class="com.zeh.main.pojo.DiModel">
<constructor-arg type="int" value="22"/>
<constructor-arg type="java.lang.String" value="eric" />
<constructor-arg type="java.lang.String" value="我是通过构造器的参数类型注入的" />
</bean>
上面bean xml配置创建的DiModel相当于如下java代码:
DiModel diModel = new DiModel("eric", 22, "我是通过构造器的参数类型注入的");
测试结果:
bean is DiModel{name = 'eric',age = '22',desc = '我是通过构造器的参数类型注入的'}
优缺点:
实际上按照参数类型或者参数位置去注入都存在一个明显的问题,很难通过bean的配置文件,知道这个参数对应DiModel中的哪个属性。
代码的可读性不好,比如我想知道每个参数对应DiModel中的哪个属性,必须去看DiModel的源码。
下面介绍按照构造器参数名称去注入的方式比上面这2种注入方式更优雅一些。
3.3 根据构造器参数名称注入依赖
用法:
<!-- 通过构造器参数的名称注入 -->
<bean id="diModel" class="com.zeh.main.pojo.DiModel">
<constructor-arg name="参数名称" value="参数值" />
<constructor-arg name="参数名称" value="参数值" />
</bean>
constructor-arg标签用于让用户指定构造器参数。
name:构造方法参数名称
value:构造方法参数的值,value只能用来给简单类型设置值
java通过反射的方式可以获取到方法的参数名称,不过源码中的参数经过编译之后变成class文件,通常情况下源码编译成class文件之后,编译器会对参数名称做优化。
即源码中的参数真实名称会丢失,参数名称会编译成arg0,arg1,arg2之类的,和实际参数不一样了。
如果需要将源码中的参数名称保留到编译后的class文件中,编译的时候需要使用下面的命令:
javac -parameters java源码
然而我们很难保证在编译代码的时候,开发人员一定会带上-parameters参数,所以方法的参数可能会在class文件中丢失,导致反射获取到的参数名称和实际参数名称不符,这个需要了解下。
参数名称可能不稳定的问题,spring本身也提供了解决方案,通过ConstructorProperties注解来定义构造方法参数的名称,将这个直接加到构造方法上面,如下:
@ConstructorProperties({"第1个参数名称", "第2个参数名称",...})
public 类名(String p1,String p2,String p3,....,参数n){}
bean xml:
<!-- 通过构造器参数的名称注入 -->
<bean id="diModel" class="com.zeh.main.pojo.DiModel">
<constructor-arg name="age" value="19"/>
<constructor-arg name="name" value="eric" />
<constructor-arg name="desc" value="我是通过构造器的参数名称注入的" />
</bean>
上面bean xml配置创建的DiModel相当于如下java代码:
DiModel diModel = new DiModel("eric", 22, "我是通过构造器的参数名称注入的");
修改DiModel的构造方法:
@ConstructorProperties({"name", "desc"})
public DiModel(String name, String desc) {
this.name = name;
this.desc = desc;
}
@ConstructorProperties({"name", "age", "desc"})
public DiModel(String name, int age, String desc) {
this.name = name;
this.age = age;
this.desc = desc;
}
测试结果:
bean is DiModel{name = 'eric',age = '19',desc = '我是通过构造器的参数名称注入的'}
4. spring完成依赖注入的方式-通过set属性注入
4.1 setter属性注入的前提
通常情况下,我们的类都是标准的javabean,javabean类的特点:
- 属性都是private访问级别的。
- 属性通常通过一组setter(修改器)和getter(访问器)方法来访问。
- setter方法,以set开头,后跟首字母大写的属性名,比如:setUserName,简单属性一般只有一个方法参数,方法返回值通常是void。
- getter方法,以get开头,对于boolean类型的属性一般以is开头,后跟首字母大写的属性名,比如:getUserName,isOk。
spring对符合javabean特点的类,提供了setter属性方式的注入,会调用对应属性的setter方法将被依赖的对象注入进去。
4.2 setter属性方式注入
用法:
<!-- 通过setter属性方式注入 -->
<bean id="diModel" class="com.zeh.main.pojo.DiModel">
<property name="属性名称" value="属性值"/>
...
<property name="属性名称" value="属性值"/>
</bean>
property标签用于对当前bean中的属性的值进行配置,可以有多个。
name:属性的名称。
value:属性的值。
bean xml:
<!-- 通过setter属性方式注入 -->
<bean id="diModel" class="com.zeh.main.pojo.DiModel">
<property name="name" value="eric"/>
<property name="age" value="18"/>
<property name="desc" value="我是通过setter属性注入的"/>
</bean>
上面bean xml配置创建的DiModel相当于如下java代码:
DiModel diModel = new DiModel();
diModel.setName("eric");
diModel.setAge(18);
diModel.setDesc("我是通过setter属性注入的");
修改DiModel,删除有参构造,加上对应的setter方法:
package com.zeh.main.pojo;
import java.beans.ConstructorProperties;
public class DiModel {
private String name;
private int age;
private String desc;
public DiModel() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "DiModel{" + "name = '" + this.name + "',age = '" + this.age + "',desc = '" + this.desc + "'}";
}
}
测试结果:
bean is DiModel{name = 'eric',age = '18',desc = '我是通过setter属性注入的'}
优缺点:
setter注入相比较于构造方法注入要灵活一些,构造方法注入属性的话,需要指定对应构造方法中所有参数的值,而setter注入的方式没有这种限制。
只要对应属性有setter方法,就可以按照需要,选择注入哪些属性的值。
5. 注入容器中的其他bean-引用对象注入
5.1 引用对象的注入
上面为目标bean注入的都是普通类型的属性对象,即简单类型。通过value属性来设置需要注入的对象的值,value属性的值是String类型的,spring容器内部会自动将value的值转换为对象的实际类型。
如果我们依赖的属性是容器中的其他bean对象时,需要使用下面的方式进行注入。
5.2 注入容器中的bean
注入容器中的bean有2种写法:
1. ref属性方式注入
2. 内置bean的方式注入
5.3 ref属性方式注入
将上面介绍的constructor-arg标签或者property标签中的value属性替换为ref属性,ref属性的值为容器中其他bean的名称。
比如:
构造器方式,将value替换为ref:
<constructor-arg ref="需要注入的bean名称" />
setter方式,将value替换为ref:
<property name="属性名称" ref="需要注入的bean名称"/>
5.4 内置bean方式注入
构造器方式:
<constructor-arg index="">
<bean class=""/>
</constructor-arg>
setter方式:
<property name="">
<bean class=""/>
</property>
5.5 案例
DiModel:
package com.zeh.main.pojo;
public class DiModel {
private String name;
private int age;
private String desc;
public DiModel() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "DiModel{" + "name = '" + this.name + "',age = '" + this.age + "',desc = '" + this.desc + "'}";
}
}
UserModel:
package com.zeh.main.pojo;
public class UserModel {
private String userName;
private int userAge;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getUserAge() {
return userAge;
}
public void setUserAge(int userAge) {
this.userAge = userAge;
}
@Override
public String toString() {
return "UserModel{" + "userName = '" + this.userName + "',userAge = '" + this.userAge + "'}";
}
}
PersonModel:
package com.zeh.main.pojo;
public class PersonModel {
private DiModel diModel;
private UserModel userModel;
public DiModel getDiModel() {
return diModel;
}
public void setDiModel(DiModel diModel) {
this.diModel = diModel;
}
public UserModel getUserModel() {
return userModel;
}
public void setUserModel(UserModel userModel) {
this.userModel = userModel;
}
@Override
public String toString() {
return "PersonModel{" + "diModel = '" + this.diModel + ",userModel = '" + this.userModel + "'}";
}
}
bean xml配置:
<!-- 通过setter属性方式注入 -->
<bean id="diModel" class="com.zeh.main.pojo.DiModel">
<property name="name" value="eric"/>
<property name="age" value="18"/>
<property name="desc" value="我是通过setter属性注入的"/>
</bean>
<!-- 实例化 PersonModel -->
<bean id="personModel" class="com.zeh.main.pojo.PersonModel">
<!-- diModel通过ref方式注入 -->
<property name="diModel" ref="diModel"/>
<property name="userModel">
<!-- userModel通过内部bean方式注入 -->
<bean class="com.zeh.main.pojo.UserModel">
<property name="userName" value="daisy"/>
<property name="userAge" value="15"/>
</bean>
</property>
</bean>
测试程序:
package com.zeh.main.controller;
import com.zeh.main.util.IocUtil;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DiModelController {
public static void main(String[] args) {
String beanXml = "classpath:/spring/applicationContext.xml";
ClassPathXmlApplicationContext context = IocUtil.getIocContext(beanXml);
System.out.println(String.format("bean is %s", context.getBean("personModel")));
}
}
测试结果:
bean is PersonModel{diModel = 'DiModel{name = 'eric',age = '18',desc = '我是通过setter属性注入的'},userModel = 'UserModel{userName = 'daisy',userAge = '15'}'}l
6. 其他复合类型注入
6.1 语法
注入java.util.List(list元素):
<bean id="" class="">
<property name="">
<list>
<value>spring</value>
或
<ref bean="bean名称"/>
或
<list></list>
或
<bean></bean>
或
<array></array>
或
<map></map>
</list>
</property>
</bean>
注入java.util.Set(set元素):
<bean id="" class="">
<property name="">
<set>
<value>spring</value>
或
<ref bean="bean名称"/>
或
<list></list>
或
<bean></bean>
或
<array></array>
或
<map></map>
</set>
</property>
</bean>
注入java.util.Map(map元素):
<bean id="" class="">
<property name="">
<map>
<entry key="eric" value="30" key-ref="key引用的bean名称" value-ref="value引用的bean名称" value-type="value的类型"/>
或
<entry>
<key>
key的名称
</key>
<value>
value的值,可以为任意类型
</value>
</entry>
</map>
</property>
</bean>
注入数组(array元素):
<bean id="" class="">
<property name="">
<array>
<value>spring</value>
或
<ref bean="bean名称"/>
或
<list></list>
或
<bean></bean>
或
<array></array>
或
<map></map>
</array>
</property>
</bean>
注入java.util.Properties(props元素):
Properties类相当于键值都是String类型的Map对象,使用props进行注入,如下:
<bean id="" class="">
<property name="">
<props>
<prop key="key1">java高并发</prop>
<prop key="key2">java多线程</prop>
<prop key="key3">spring进阶大全</prop>
</props>
</property>
</bean>
6.2 复合类型注入综合案例
OtherModel:
package com.zeh.main.pojo;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class OtherModel {
private List<String> myList;
private Set<UserModel> mySet;
private Map<String, Integer> myMap;
private int[] myArray;
private Properties myProperties;
public List<String> getMyList() {
return myList;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public Set<UserModel> getMySet() {
return mySet;
}
public void setMySet(Set<UserModel> mySet) {
this.mySet = mySet;
}
public Map<String, Integer> getMyMap() {
return myMap;
}
public void setMyMap(Map<String, Integer> myMap) {
this.myMap = myMap;
}
public int[] getMyArray() {
return myArray;
}
public void setMyArray(int[] myArray) {
this.myArray = myArray;
}
public Properties getMyProperties() {
return myProperties;
}
public void setMyProperties(Properties myProperties) {
this.myProperties = myProperties;
}
@Override
public String toString() {
return "OtherModel{" + "myList = '" +
this.myList + "',mySet = '" +
this.mySet + "',myMap = " +
this.myMap + "',myArray = '" +
Arrays.toString(myArray) + "',myProperties = '" +
this.myProperties + "'}";
}
}
bean xml:
<bean id="user1" class="com.zeh.main.pojo.UserModel">
<property name="userName" value="eric" />
<property name="userAge" value="18" />
</bean>
<bean id="user2" class="com.zeh.main.pojo.UserModel">
<property name="userName" value="daisy" />
<property name="userAge" value="17" />
</bean>
<bean id="other" class="com.zeh.main.pojo.OtherModel">
<!-- 注入java.util.List对象 -->
<property name="myList">
<list>
<value>spring</value>
<value>springboot</value>
</list>
</property>
<!-- 注入java.util.Set对象 -->
<property name="mySet">
<set>
<ref bean="user1" />
<ref bean="user2" />
<bean class="com.zeh.main.pojo.UserModel">
<property name="userName" value="内置bean" />
<property name="userAge" value="100" />
</bean>
<ref bean="user1"/>
</set>
</property>
<!-- 注入java.util.Map对象 -->
<property name="myMap">
<map>
<entry key="yu" value="18"/>
<entry key="zhao" value="22"/>
</map>
</property>
<!-- 注入数组对象 -->
<property name="myArray">
<array>
<value>123</value>
<value>158</value>
<value>789</value>
</array>
</property>
<!-- 注入java.util.Properties对象 -->
<property name="myProperties">
<props>
<prop key="key1">java开发实战</prop>
<prop key="key2">java高并发系列</prop>
<prop key="key3">java框架详解</prop>
</props>
</property>
</bean>
测试:
package com.zeh.main.controller;
import com.zeh.main.util.IocUtil;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class OtherController {
public static void main(String[] args) {
String beanXml = "classpath:/spring/applicationContext.xml";
ClassPathXmlApplicationContext context = IocUtil.getIocContext(beanXml);
System.out.println(String.format("bean is %s", context.getBean("other")));
}
}
测试结果:
bean is OtherModel{myList = '[spring, springboot]',mySet = '[UserModel{userName = 'eric',userAge = '18'}, UserModel{userName = 'daisy',userAge = '17'}, UserModel{userName = '内置bean',userAge = '100'}]',myMap = {yu=18, zhao=22}',myArray = '[123, 158, 789]',myProperties = '{key3=java框架详解, key2=java高并发系列, key1=java开发实战}'}
7. 总结
- 本文主要讲解了xml中bean的依赖注入方式,都是采用硬编码到xml文件中的方式进行的,这种方式算是手动注入的方式。
- 注入简单类型通过value属性或者value标签设置需要注入的值即可;注入对象如果是容器中的其他bean的时候,需要使用ref属性或者ref标签或者内置bean元素的方式注入即可。
- 还有其他几种类型的注入,比如List,Set,Map,Array,Properties类型的注入。
文档信息
- 本文作者:Marshall