Spring的依赖注入

时间:2022-07-25
本文章向大家介绍Spring的依赖注入,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

依赖注入(Dependency Injection 简称DI),是Spring的IOC控制反转(Inversion of Control,英文缩写为IOC)模块中比较重要的一个功能,所谓依赖注入就是在创建一个对象时,将这个对象所依赖的对象或数据都创建好放进去,例如有一个Student类,它的构造器要求传递一个Dog对象,也就是说它依赖这个Dog对象,或者它有一个String类型的属性,那么它也就依赖String类型的数据。通过Spring的配置文件,我们可以配置好某个对象的依赖,当该对象被实例化时一并将它的依赖创建好给它,这个过程就是依赖注入。

在Spring的配置文件中,我们通过bean标签来配置需要被管理的类,配置好后Spring就可以帮我们实例化这个类的对象,我们就只需要从Spring容器中获取这个对象即可,不用自己手动去new,先来看看如何让Spring来帮我们创建对象,Student类代码如下:

package org.zero01.test;

public class Student {

    private String name;
    private int sid;
    private String address;

    public String getName() {
        return name;
    }

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

    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

Spring配置文件配置内容如下:

<?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.xsd">

    <bean id="stu" class="org.zero01.test.Student"></bean>

</beans>

测试代码:

package org.zero01.test;

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

public class Test {

    public static void main(String[] args) {

        // 传入配置文件的路径
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通过配置的id创建对象
        Student student = (Student) app.getBean("stu");
        student.setSid(1);
        student.setName("小明");
        student.setAddress("M78星云");

        System.out.println(student.getSid());
        System.out.println(student.getName());
        System.out.println(student.getAddress());

        // 默认是单例模式的,所以得到的是同一个对象
        Student student2 = (Student) app.getBean("stu");
        System.out.println(student == student2);
    }
}

运行结果:

1
小明
M78星云
true

默认情况下,Spring实例化的对象都是单例的,如果不希望是单例的话,将bean标签中的scope属性设置为prototype即可:

<bean id="stu" class="org.zero01.test.Student" scope="prototype"></bean>

测试代码:

package org.zero01.test;

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

public class Test {

    public static void main(String[] args) {

        // 传入配置文件的路径
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通过配置的id创建对象
        Student student = (Student) app.getBean("stu");

        // 设置成prototype后,每次创建的都是新对象了
        Student student2 = (Student) app.getBean("stu");
        System.out.println(student == student2);
    }
}

运行结果:

false

接下来介绍一下bean标签中的一些常用属性以及内嵌标签

bean标签中的init-method属性,该属性指定一个方法,这个方法会在容器实例化对象时被调用,例如我在Student类中增加一个init方法:

public void init() {
        this.name = "小明";
        this.sid = 1;
        this.address = "M78星云";
}

在init-method属性中指定这个方法:

<bean id="stu" class="org.zero01.test.Student" init-method="init"></bean>

测试代码:

package org.zero01.test;

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

public class Test {

    public static void main(String[] args) {

        // 传入配置文件的路径
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通过配置的id创建对象
        Student student = (Student) app.getBean("stu");

        System.out.println(student.getSid());
        System.out.println(student.getName());
        System.out.println(student.getAddress());
    }
}

运行结果:

1
小明
M78星云

bean标签中有一个 property 子标签,通过 property 标签我们可以配置该对象的属性值,例如

    <bean id="stu" class="org.zero01.test.Student">
        <property name="sid" value="1"/>
        <property name="name" value="Jon"/>
        <property name="address" value="北京"/>
    </bean>

测试代码和之前一样,略。运行结果如下:

1
Jon
北京

需要注意的是,想要通过 property 标签去配置对象中某个属性的值,那么这个属性必须具备有setter方法,否则是不能配置的。

property 标签中有一个ref属性,这个属性的值为bean标签的id属性的值,所以说当一个对象依赖某个对象时,就可以使用到ref属性来进行引用,例如Student的属性里依赖了一个Dog对象:

    private Dog dog;

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

通过ref属性引用这个对象即可:

    <bean id="dog" class="org.zero01.test.Dog"/>
    <bean id="stu" class="org.zero01.test.Student">
        <!-- 基本数据类的值都可以自动进行转换 -->
        <property name="sid" value="1"/>
        <property name="name" value="Jon"/>
        <property name="address" value="北京"/>
        <property name="dog" ref="dog"/>
    </bean>

以上已经将 property 标签的属性介绍完了,因为 property 标签就只有这个三个属性,但是它的子标签却有不少,例如那三个属性都可以作为子标签:

    <bean id="dog" class="org.zero01.test.Dog"/>
    <bean id="stu" class="org.zero01.test.Student">
        <property name="sid">
            <value type="int">1</value>
        </property>
        <property name="name">
            <value type="java.lang.String">Jon</value>
        </property>
        <property name="address" value="北京"/>
        <property name="dog">
            <ref bean="dog"/>
        </property>
    </bean>

property 标签里常用的子标签:

  • value 指定属性的值
  • ref 引用bean对象
  • bean 创建一个bean对象
  • list 指定List类型的属性值
  • map 指定map类型的属性值
  • set 指定set类型的属性值
  • array 指定数组类型的属性值
  • null 指定一个空属性值
  • props 指定Properties类型的属性值

value和ref标签已经使用过了,剩下的其他标签的使用方式如下:

Student类增加以下内容:

    private List list;
    private Set set;
    private Map map;
    private Properties properties;
    private int[] numbers;

    public Properties getProperties() {
        return properties;
    }

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

    public int[] getNumbers() {
        return numbers;
    }

    public void setNumbers(int[] numbers) {
        this.numbers = numbers;
    }

    public List getList() {
        return list;
    }

    public void setList(List list) {
        this.list = list;
    }

    public Set getSet() {
        return set;
    }

    public void setSet(Set set) {
        this.set = set;
    }

    public Map getMap() {
        return map;
    }

    public void setMap(Map map) {
        this.map = map;
    }

配置文件内容:

    <bean id="dog" class="org.zero01.test.Dog"/>
    <bean id="stu" class="org.zero01.test.Student">

        <property name="list">
            <list>
                <value>list01</value>
                <value>list02</value>
                <value>list03</value>
                <bean class="org.zero01.test.Dog" id="dog2"/>
                <ref bean="dog"></ref>
                <null/>
            </list>
        </property>

        <property name="map">
            <map key-type="java.lang.String" value-type="java.lang.Object">
                <entry key="01" value="零一"/>
                <entry key="02" value="零二"/>
                <entry key="03" value="零三"/>
                <entry key="dog" value-ref="dog" />
            </map>
        </property>

        <property name="set">
            <set>
                <value>set01</value>
                <value>set02</value>
                <value>set03</value>
                <null/>
            </set>
        </property>

        <property name="numbers">
            <array>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </array>
        </property>

        <property name="properties">
            <props>
                <prop key="stature">1.75</prop>
                <prop key="avoirdupois">100</prop>
            </props>
        </property>
    </bean>

测试代码:

package org.zero01.test;

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

public class Test {

    public static void main(String[] args) {

        // 传入配置文件的路径
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通过配置的id创建对象
        Student student = (Student) app.getBean("stu");

        System.out.println(student.getList());

        System.out.println(student.getMap());

        System.out.println(student.getSet());

        for (int i : student.getNumbers()) {
            System.out.print(i);
        }

        System.out.println("n" + student.getProperties());
    }
}

运行结果:

[list01, list02, list03, org.zero01.test.Dog@f6c48ac, org.zero01.test.Dog@13deb50e, null]
{01=零一, 02=零二, 03=零三, dog=org.zero01.test.Dog@13deb50e}
[set01, set02, set03, null]
123
{avoirdupois=100, stature=1.75}

如上,可以看到,Spring配置文件的标签还是很丰富的,这还只是基本的常用标签,一些额外的标签支持需要自己引入。

除了 property 标签用于配置属性值外,还有一个 constructor-arg 标签,这个标签可以配置构造器的参数值,使用方式和 property 标签基本一样,例如Student类里有这样一个构造器:

    public Student(String name, int sid, String address, Dog dog) {
        this.name = name;
        this.sid = sid;
        this.address = address;
        this.dog = dog;
    }

则配置内容如下:

    <bean id="stu" class="org.zero01.test.Student">
        <constructor-arg name="dog" ref="dog"/>
        <constructor-arg name="sid" value="1"/>
        <constructor-arg name="name" value="Mick"/>
        <constructor-arg name="address" value="上海"/>
    </bean>

测试代码:

package org.zero01.test;

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

public class Test {

    public static void main(String[] args) {

        // 传入配置文件的路径
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通过配置的id创建对象
        Student student = (Student) app.getBean("stu");
        System.out.println(student.getSid());
        System.out.println(student.getName());
        System.out.println(student.getAddress());
        System.out.println(student.getDog());
    }
}

运行结果:

1
Mick
上海
org.zero01.test.Dog@7cd62f43

constructor-arg 标签除了以上两个使用到的属性之外还有一个index属性以及type属性,index属性是用于指定给哪个位置的参数赋值,而type属性则是用于指定该值的类型,这两个属性一般用不到。constructor-arg 标签也有子标签,它的子标签和 property 标签的子标签一样,这里就不再赘述了。

我们在使用 property 标签的时候,可能会感到一丝蛋疼,要写那么多的属性或标签,所以Spring就提供了一个属性标记,让我们可以通过这个属性标记来简化一些配置 property 的操作,要使用这个属性标记首先需要在 beans 引入属性标记地址:

xmlns:p="http://www.springframework.org/schema/p"

然后就可以使用这个属性标记了:

    <bean id="dog" class="org.zero01.test.Dog"/>
    <bean id="list" class="java.util.ArrayList"/>
    <bean id="stu" class="org.zero01.test.Student" p:sid="1" p:name="Alisa" p:address="湖南" p:dog-ref="dog" p:list-ref="list"/>

从配置内容可以看到,在bean标签上就可以直接完成属性的配置了,能让我们少写不少标签。但是有一个小缺点就是不能够给集合这种对象填充元素,从以上的配置内容中也可以看到只能给一个不包含任何元素的实例对象。所以这个属性标记一般用于配置基本数据类型的属性值多些,遇到集合对象需要填充元素的情况就只能使用 property 标签了。

测试代码与之前的差不多,略。运行结果如下:

1
Alisa
湖南
org.zero01.test.Dog@271053e1
[]

在实际开发中,一般用xml配置依赖对象的情况比较少,基本大部分情况都是使用注解去进行配置,因为注解要比xml方便和简单。但是有一些对象则必须要在xml里配置,例如用于连接数据库的数据源对象,因为这种对象的配置信息多变动,使用注解来配置就不合适了,所以这种类型的对象就十分适合使用xml来进行配置,例如配置个 c3p0 连接池:

    <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource"
          p:driverClass="com.mysql.jdbc.Driver"
          p:jdbcUrl="jdbc:mysql///mysql"
          p:user="root"
          p:password="your_password"
          p:maxPoolSize="10"
          p:minPoolSize="3"
          p:loginTimeout="2000"
    />

测试代码:

package org.zero01.test;

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

import javax.sql.DataSource;

public class Test {

    public static void main(String[] args) {

        // 传入配置文件的路径
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通过配置的id创建对象
        DataSource dataSource = (DataSource) app.getBean("c3p0");
        System.out.println(dataSource);
    }
}

运行结果:

com.mchange.v2.c3p0.ComboPooledDataSource[ identityToken -> 1hge0yy9t1mjbsw5vgbf8b|30b8a058, dataSourceName -> 1hge0yy9t1mjbsw5vgbf8b|30b8a058 ]

bean 标签里有一个 abstract 属性,该属性可以将一个节点声明为抽象节点,抽象节点可以被子节点继承,与Java中的继承概念是一样的,子节点继承父节点后可以拥有父节点的所有配置信息。例如我们可以通过配置数据源对象的来演示这种继承关系:

    <!-- 通过 abstract 属性声明为抽象节点后才可以被继承 -->
    <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource" abstract="true"
          p:driverClass="com.mysql.jdbc.Driver"
          p:jdbcUrl="jdbc:mysql///mysql"
          p:user="root"
          p:password="your_password"
    />

    <!-- 通过 parent 属性来继承一个父节点,该节点会拥有父节点的所有配置信息 -->
    <bean id="dataSource" parent="c3p0"
          p:maxPoolSize="20"
          p:minPoolSize="5"
          p:loginTimeout="1500"
    />

    <bean id="dataSource2" parent="c3p0"
          p:maxPoolSize="15"
          p:minPoolSize="3"
          p:loginTimeout="1000"
    />

如上,子节点继承父节点后可以拥有父节点的所有配置信息,所以我们可以把一些较为稳定的,不易改变的配置信息写在父节点上。然后在父节点的配置信息的基础上,子节点可以新增一些配置信息,这样我们在获得数据源对象的时候就有多个配置方案可以选择。

测试代码将之前的c3p0改成dataSource或dataSource2即可,运行结果如下:

com.mchange.v2.c3p0.ComboPooledDataSource[ identityToken -> 1hge0yy9t1mjprn91mvr8fv|30b8a058, dataSourceName -> 1hge0yy9t1mjprn91mvr8fv|30b8a058 ]

注:一旦某个节点声明为抽象节点后就不可以被实例化了,只能实例化继承它的子节点。


配置Spring的注解支持

以上也提到了使用注解来配置依赖对象会方便简单一些,所以以下简单介绍一下如何配置Spring的注解,让Spring能过够通过注解的方式来对类进行管理。

Spring配置文件内容修改如下:

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 让Spring支持注解 -->
    <context:annotation-config/>
    <!-- 指定哪些包下的类支持使用注解的方式管理 -->
    <context:component-scan base-package="org.zero01.test"/>

</beans>

在Student类上加上 @Component 注解,让该类受Spring管理:

package org.zero01.test;

import org.springframework.stereotype.Component;

// 这里注解配置的值相当于xml中bean标签的id属性的值
@Component("stu")
public class Student {
        ...

测试代码:

package org.zero01.test;

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

public class Test {

    public static void main(String[] args) {

        // 传入配置文件的路径
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通过配置的id创建对象
        Student student = (Student) app.getBean("stu");
        Student student2 = (Student) app.getBean("stu");

        // 同样默认是单例的
        System.out.println(student == student2);
    }
}

运行结果:

true

在属性的setter方法上,我们可以通过 @Value 注解来配置属性的值,这就相当于xml中的 property 标签的作用了:

package org.zero01.test;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("stu")
public class Student {

    private String name;
    private int sid;
    private String address;

    public String getName() {
        return name;
    }

    @Value("小明")
    public void setName(String name) {
        this.name = name;
    }

    public int getSid() {
        return sid;
    }

    @Value("1")
    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getAddress() {
        return address;
    }

    @Value("湖南")
    public void setAddress(String address) {
        this.address = address;
    }
}

测试代码:

package org.zero01.test;

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

public class Test {

    public static void main(String[] args) {

        // 传入配置文件的路径
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通过配置的id创建对象
        Student student = (Student) app.getBean("stu");
        System.out.println(student.getSid());
        System.out.println(student.getName());
        System.out.println(student.getAddress());
    }
}

运行结果:

1
小明
湖南

这个 @Value 注解除了可以写在属性的setter方法上,还可以直接写在属性上,作用是一样的:

package org.zero01.test;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("stu")
public class Student {

    @Value("小明")
    private String name;
    @Value("1")
    private int sid;
    @Value("湖南")
    private String address;
    ...

测试代码和之前一样,运行结果也是一样,略。