spring手动注入依赖

2022/01/07 spring专题 共 16185 字,约 47 分钟
闷骚的程序员

之前在引出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类的特点:

  1. 属性都是private访问级别的。
  2. 属性通常通过一组setter(修改器)和getter(访问器)方法来访问。
  3. setter方法,以set开头,后跟首字母大写的属性名,比如:setUserName,简单属性一般只有一个方法参数,方法返回值通常是void。
  4. 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. 总结

  1. 本文主要讲解了xml中bean的依赖注入方式,都是采用硬编码到xml文件中的方式进行的,这种方式算是手动注入的方式。
  2. 注入简单类型通过value属性或者value标签设置需要注入的值即可;注入对象如果是容器中的其他bean的时候,需要使用ref属性或者ref标签或者内置bean元素的方式注入即可。
  3. 还有其他几种类型的注入,比如List,Set,Map,Array,Properties类型的注入。

文档信息

Search

    Table of Contents