Spring基础:依赖注入

目录

依赖注入

依赖注入(Dependency Injection, DI)是一种设计模式,也是Spring框架的核心概念之一。现在我们已经了解了如何注册和使用一个Bean,但是这样还远远不够,还记得我们一开始说的,消除类之间的强关联吗?比如现在有一个教师接口:

1
2
3
public interface Teacher {
    void teach();
}

具体的实现有两个:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class ArtTeacher implements Teacher{
    @Override
    public void teach() {
        System.out.println("我是美术老师,我教你画画!");
    }
}
public class ProgramTeacher implements Teacher{
    @Override
    public void teach() {
        System.out.println("我是编程老师,我教你学Golang!");
    }
}

我们的学生一开始有一个老师教他,比如美术老师:

1
2
3
4
5
6
7
public class Student {
    private Teacher teacher = new ArtTeacher();   
  	//在以前,如果我们需要制定哪个老师教我们,直接new创建对应的对象就可以了
    public void study(){
        teacher.teach();
    }
}

但是我们发现,如果美术老师不教了,现在来了一个其他的老师教学生,那么就需要去修改Student类的定义:

1
2
3
public class Student {
    private Teacher teacher = new ProgramTeacher();
  	...

可以想象一下,如果现在冒出来各种各样的类都需要这样去用Teacher,那么一旦Teacher的实现发生变化,会导致我们挨个对之前用到Teacher的类进行修改,这就很难受了。

而有了依赖注入之后,Student中的Teacher成员变量,可以由IoC容器来选择一个合适的Teacher对象进行赋值,也就是说,IoC容器在创建对象时,需要将我们预先给定的属性注入到对象中,非常简单,我们可以使用property标签来实现,我们将bean标签展开:

1
2
3
4
<bean name="teacher" class="com.test.bean.ProgramTeacher"/>
<bean name="student" class="com.test.bean.Student">
    <property name="teacher" ref="teacher"/>
</bean>

同时我们还需要修改一下Student类,依赖注入要求对应的属性必须有一个set方法:

1
2
3
4
5
6
7
public class Student {
    private Teacher teacher;
  	//要使用依赖注入,我们必须提供一个set方法(无论成员变量的访问权限是什么)命名规则依然是驼峰命名法
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
    ...

使用property来指定需要注入的值或是一个Bean,这里我们选择ProgramTeacher,那么在使用时,Student类中的得到的就是这个Bean的对象了:

1
2
Student student = context.getBean(Student.class);
student.study();

可以看到,现在我们的Java代码中,没有出现任何的具体实现类信息(ArtTeacher、ProgramTeacher都没出现)取而代之的是那一堆xml配置,这样,就算我们切换老师的实现为另一个类,也不用去调整代码,只需要变动一下Bean的类型就可以:

1
2
3
4
5
<!--  只需要修改这里的class即可,现在改为ArtTeacher  -->
<bean name="teacher" class="com.test.bean.ArtTeacher"/>
<bean name="student" class="com.test.bean.Student">
    <property name="teacher" ref="teacher"/>
</bean>

这样,这个Bean的class就变成了新的类型,并且我们不需要再去调整其他位置的代码,再次启动程序:

通过依赖注入,是不是开始逐渐感受到Spring为我们带来的便利了?当然,依赖注入并不一定要注入其他的Bean,也可以是一个简单的值:

1
2
3
<bean name="student" class="com.test.bean.Student">
    <property name="name" value="卢本伟"/>
</bean>

直接使用value可以直接传入一个具体值。

实际上,在很多情况下,类中的某些参数是在构造方法中就已经完成的初始化,而不是创建之后,比如:

1
2
3
4
5
6
7
public class Student {
    private final Teacher teacher;   //构造方法中完成,所以说是一个final变量

    public Student(Teacher teacher){   //Teacher属性是在构造方法中完成的初始化
        this.teacher = teacher;
    }
  	...

我们前面说了,Bean实际上是由IoC容器进行创建的,但是现在我们修改了默认的无参构造,可以看到配置文件里面报错了:

很明显,是因为我们修改了构造方法,IoC容器默认只会调用无参构造,所以,我们需要指明一个可以用的构造方法,我们展开bean标签,添加一个constructor-arg标签:

1
2
3
4
<bean name="teacher" class="com.test.bean.ArtTeacher"/>
<bean name="student" class="com.test.bean.Student">
    <constructor-arg name="teacher" ref="teacher"/>
</bean>

这里的constructor-arg就是构造方法的一个参数,这个参数可以写很多个,会自动匹配符合里面参数数量的构造方法,这里匹配的就是我们刚刚编写的需要一个参数的构造方法。

通过这种方式,我们也能实现依赖注入,只不过现在我们将依赖注入的时机提前到了对象构造时。

那要是出现这种情况呢?现在我们的Student类中是这样定义的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Student {
    private final String name;
    public Student(String name){
        System.out.println("我是一号构造方法");
        this.name = name;
    }

    public Student(int age){
        System.out.println("我是二号构造方法");
        this.name = String.valueOf(age);
    }
}

此时我们希望使用的是二号构造方法,那么怎么才能指定呢?有2种方式,我们可以给标签添加类型:

1
<constructor-arg value="1" type="int"/>

也可以指定为对应的参数名称:

1
<constructor-arg value="1" name="age"/>

反正只要能够保证我们指定的参数匹配到目标构造方法即可。

现在我们的类中出现了一个比较特殊的类型,它是一个集合类型:

1
2
3
4
5
6
7
public class Student {
    private List<String> list;

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

对于这种集合类型,有着特殊的支持:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<bean name="student" class="com.test.bean.Student">
  	<!--  对于集合类型,我们可以直接使用标签编辑集合的默认值  -->
    <property name="list">
        <list>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </list>
    </property>
</bean>

不仅仅是List,Map、Set这类常用集合类包括数组在内,都是支持这样编写的,比如Map类型,我们也可以使用entry来注入:

1
2
3
4
5
6
7
8
9
<bean name="student" class="com.test.bean.Student">
    <property name="map">
        <map>
            <entry key="语文" value="100.0"/>
            <entry key="数学" value="80.0"/>
            <entry key="英语" value="92.5"/>
        </map>
    </property>
</bean>

至此,我们就已经完成了两种依赖注入的学习:

  • Setter依赖注入:通过成员属性对应的set方法完成注入。
  • 构造方法依赖注入:通过构造方法完成注入。
0%