# Spring教程 - 4 IoC容器 - 基于XML配置(1)

# 4.1 获取Bean

在介绍 IoC 之前,先介绍一下获取 bean 的方式。

# 1 通过id获取bean

在 HelloWorld 程序中,因为已经在 XML 中指定了 bean 标签的 id,所以可以根据 id 获取到 bean 对象。

如下:

// 1. 加载spring的配置文件,在类路径下加载,类路径指的是src/main/resources
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
// 2. 通过ID获取配置创建的对象
UserService userService = (UserService) context.getBean("userService");
// 3. 调用对象的方法
userService.getUser();
1
2
3
4
5
6

# 2 通过类型获取bean

我们还可以通过类型获取 bean,通过 UserService 类型获取 bean。

举个栗子:

// 1. 加载spring的配置文件,在类路径下加载,类路径指的是src/main/resources
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
// 2. 从spring容器中获取对象
UserService userService = (UserService) context.getBean(UserService.class);  // 通过类型获取
// 3. 调用对象的方法
userService.getUser();
1
2
3
4
5
6
  • 在上面额代码中,通过 UserService.class 类型获取 bean 对象。

但是这里需要注意,通过类型获取 bean,需要保证 IoC 容器中该类型的 bean 只能有一个。

例如我们使用相同的类型,配置了两个 bean:

<bean id="userService1" class="com.foooor.hellospring.UserService">
</bean>

<bean id="userService2" class="com.foooor.hellospring.UserService">
</bean>
1
2
3
4
5

那么在通过类型获取 bean 的时候,就会报错,提示不唯一,因为或找到多个 bean。


如果一个类继承了一个接口,那么我们也可以通过接口来获取到 bean:

例如,UserService 实现了 IUserService 接口,那么可以通过如下方式获取:

IUserService userService = context.getBean(IUserService.class);  // 通过类型获取
1

但是需要注意,如果接口有多个实现类,那么就不可以通过这种方式获取了,因为 bean 又不唯一了。

# 3 通过id和类型获取bean

还可以同时通过 id 和类型获取 bean 对象:

// 1. 加载spring的配置文件,在类路径下加载,类路径指的是src/main/resources
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
// 2. 从spring容器中获取对象
UserService userService = context.getBean("userService", UserService.class);  // 通过id和类型获取
// 3. 调用对象的方法
userService.getUser();
1
2
3
4
5
6

这种方式比直接通过 id 获取 bean 的好处就是不用进行强制类型转换,获取到的就是 UserService 类型。

推荐用这种方式。

# 4.2 setter实现依赖注入

下面开始介绍如何实现依赖注入。

我们在前面讲了,通过依赖注入(DI)实现 IoC,而依赖注入就是通过构造器或者setter方法,设置 bean 中的属性。

下面就演示一下如果实现 bean 中属性的自动注入,也就是自动赋值。

# 1 创建类

假设有一个学生类,我这里定义 Student 类如下:

package com.foooor.hellospring.pojo;

public class Student {

    private String name;
    private int age;

    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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
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
  • 类很简单,就是有两个属性,name 和 age;
  • 注意,上面的属性,一定要有 setter 方法,Spring 会调用 setter 对属性进行赋值。

下面通过 Spring 创建 Student 实例 bean,并实现属性的自动注入。

# 2 配置bean

在 bean.xml 中配置 Student bean,如下:

<!-- 配置Student对象的创建 -->
<bean id="student" class="com.foooor.hellospring.pojo.Student">
    <!-- 为属性赋值 -->
    <property name="name" value="张三"/>
    <property name="age" value="18"/>
</bean>
1
2
3
4
5
6
  • 在配置文件中,在 <bean> 中,通过 <property> 为类中的属性进行赋值,这样 Spring 在创建对象的时候,会自动将值注入到 bean 对象中。
  • 需要注意,name="age" 并不是直接给 age 赋值,而是会调用 setAge() 方法,也就是调用 set + age(首字母大写) 的方法。

# 3 测试

创建一个测试类,然后通 Spring IoC 容器中获取 Student bean 对象:

package com.foooor.hellospring;

import com.foooor.hellospring.pojo.Student;
import com.foooor.hellospring.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class StudentTest {

    @Test
    public void testStudent() {
        // 1. 加载spring的配置文件,在类路径下加载,类路径指的是src/main/resources
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        // 2. 获取spring容器中创建的对象
        Student student = context.getBean("student", Student.class);
        // 3. 打印对象
        System.out.println(student);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • 从 Spring IoC 容器中获取到 Student bean,打印对象信息,可以看到获取到了配置的信息。

执行结果:

Student{name='张三', age=18}
1

通过 setter 实现依赖注入,在开发中使用的是最多的。

# 4.3 构造器实现依赖注入

下面介绍使用构造方法来实现属性的自动注入。

# 1 创建类

还是定义 Student 类,并在类中定义构造方法。

package com.foooor.hellospring.pojo;

public class Student {

    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • Spring 会调用构造方法,为 bean 中的属性赋值。

# 2 配置bean

<!-- 配置Student对象的创建 -->
<bean id="student" class="com.foooor.hellospring.pojo.Student">
    <!-- 通过构造器为属性赋值 -->
    <constructor-arg name="name" value="张三"/>
    <constructor-arg name="age" value="18"/>
</bean>
1
2
3
4
5
6
  • <bean> 标签中,通过 <constructor-arg> 为构造方法中的参数传递值;

<constructor-arg> 还有一个 index 属性,可以通过参数的索引指定参数的值:

<constructor-arg index="0" value="张三"/>
1
  • 用的不多,一般通过 name 就好了。

# 3 测试

和上面的测试方法是一样的:

package com.foooor.hellospring;

import com.foooor.hellospring.pojo.Student;
import com.foooor.hellospring.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class StudentTest {

    @Test
    public void testStudent() {
        // 1. 加载spring的配置文件,在类路径下加载,类路径指的是src/main/resources
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        // 2. 获取spring容器中创建的对象
        Student student = context.getBean("student", Student.class);
        // 3. 打印对象
        System.out.println(student);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • 依然可以从 Spring IoC 容器中获取到 Student bean,打印对象信息。

# 4.4 不同类型数据的注入

我们已经学会了依赖注入的方式,但是在依赖注入时,不同的数据类型,注入的配置有一些区别,我们来看一下。

# 1 基本类型

例如字符串、整形、布尔值、浮点等类型,就像上面演示的一样,直接通过 <property name="name" value="value"> 标签的方式注入即可。

Student.java

public class Student {

    private String name;
    private int age;
    private double height;
    private boolean gender;
  
    // ...getters and setters.

}
1
2
3
4
5
6
7
8
9
10

bean配置:

<!-- 配置Student对象的创建 -->
<bean id="student" class="com.foooor.hellospring.pojo.Student">
    <!-- 通过setter方法为属性赋值 -->
    <property name="name" value="张三"/>
    <property name="age" value="18"/>
    <property name="height" value="1.88"/>
    <property name="gender" value="true"/>
</bean>
1
2
3
4
5
6
7
8
  • 在类中,基本数据类型也可以定义为包装类,例如 Integer,只是如果是基本类型的话,不注入值的话,默认为0(整形默认为0,boolean默认为false),如果是包装类的话,不注入值,默认为null。

但是在注入字符串的时候可能存在一个问题,就是字符串中包含特殊字符,例如 <、> 这样的特殊字符,就不能在 xml 中直接写,需要使用实体字符来代替。

举个栗子:

<!-- 报错 -->
<property name="name" value="<张三>"/>

<!-- 特殊字符需要使用实体字符代替 -->
<property name="name" value="&gt;张三&lt;"/>

<!-- 
&gt; -- >
&lt; -- <
&amp; -- &
&quot; -- "
&apos; -- '
-->
1
2
3
4
5
6
7
8
9
10
11
12
13

如果不想使用实体字符来代替,那么可以使用 CDATA节

举个栗子:

<property name="name">
    <value><![CDATA[这里面的内容,<牛逼>,不会被解析成标签或实体]]></value>
</property>
1
2
3
  • CDATA 只是XML 的语法糖,CDATA 里可以写任意字符,但 不能包含 ]]>,因为它是 CDATA 的结束符。如果确实要写,可以拆成两个CDATA,例如第一个CDATA包含前半部分 ]],后一个CDATA包含后半部分 > ,变成:<![CDATA[]]]]><![CDATA[>]]>
  • 用的不多,我是没用过。

# 2 对象类型

对象类型应该是最常用的类型了,因为在实际的开发中,基本上都会对项目进行分层,例如使用 Service 调用 Dao 等。

这里创建一个 Clazz (班级类,class是关键字,使用clazz),在 Clazz 中注入 Student ,如下:

Clazz.java

package com.foooor.hellospring.pojo;

public class Clazz {

    // 班级名称
    private String name;

    // ...getters and setters
}
1
2
3
4
5
6
7
8
9

Student.java

package com.foooor.hellospring.pojo;

public class Student {

    private String name;
    private int age;
    // 班级
    private Clazz clazz;

    // ...getters and setters
}
1
2
3
4
5
6
7
8
9
10
11
  • 上面定义了 Student 类,在 Student 类中,注入 Clazz 类。

下面通过配置 bean.xml 实现两个类对象的实例化,以及属性的依赖注入。

方式一:引用外部bean

在配置 bean.xml 的时候,对象使用 <property name="" ref=""/> 来配置:

<bean id="student" class="com.foooor.hellospring.pojo.Student">
    <property name="name" value="张三"/>
    <property name="age" value="18"/>
  
    <!-- ref指定为id="clazz" -->
    <property name="clazz" ref="clazz"/>
</bean>

<bean id="clazz" class="com.foooor.hellospring.pojo.Clazz">
    <property name="name" value="三年2班"/>
</bean>
1
2
3
4
5
6
7
8
9
10
11

上面这种方式是最常用的方式了。


方式二:内部bean

还可以使用内部 bean 的方式进行配置:

<bean id="student" class="com.foooor.hellospring.pojo.Student">
    <property name="name" value="张三"/>
    <property name="age" value="18"/>
    <property name="clazz">
        <!-- 在bean的内部定义bean,只能在当前bean内部被引用,其他的bean无法引用 -->
        <bean name="clazzInner" class="com.foooor.hellospring.pojo.Clazz">
            <property name="name" value="三年2班"/>
        </bean>
    </property>
</bean>
1
2
3
4
5
6
7
8
9
10
  • 在上面的配置中,在一个 bean 的内部定义 bean,这是内部 bean 的配置方式。

方式三:级联属性赋值

还有一种方式,可以使用级联方式赋值,配置如下:

<bean id="clazz" class="com.foooor.hellospring.pojo.Clazz">
    <property name="name" value="三年2班"/>
</bean>

<bean id="student" class="com.foooor.hellospring.pojo.Student">
    <property name="name" value="张三"/>
    <property name="age" value="18"/>

    <!-- 1.首先引用clazz对象 -->
    <property name="clazz" ref="clazz"/>
    <!-- 2. 为clazz对象的name属性赋值,会覆盖上面clazz中定义的值 -->
    <property name="clazz.name" value="三年8班"/>
</bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 首先引用外部bean,然后使用级联属性为外部 bean 的属性赋值,会覆盖 bean 的属性。

一般都用外部 bean 的方式来配置bean,另外两种用的不多。

# 3 空值null

如果要为一个属性赋值为 null,那么在配置 bean 的时候,使用如下形式:

<bean id="student" class="com.foooor.hellospring.pojo.Student">
    <property name="name">
        <!-- 赋值为null -->
        <null/>
    </property>
</bean>
1
2
3
4
5
6
  • 如果在上面不配置 name 属性,该属性也不会赋值,也会为 null。但是不同的是,配置了属性的时候,会调用 setter,传递参数 null。如果在类中,该属性有默认值,那么就会覆盖默认值。而如果不配置属性,是不会调用 setter 的,那么默认值就不会被覆盖。

需要注意和以下两种方式的区别:

<!-- 赋值为空字符串"" -->
<property name="name" value=""/>

<!-- 赋值为"null"字符串 -->
<property name="name" value=""/>
1
2
3
4
5

# 4 数组类型

为 Student 类注入数组类型的数据:

public class Student {

    private String name;
    // 数组类型的数据
    private String[] hobbies;

    // ...getters and setters
}
1
2
3
4
5
6
7
8

bean.xml 中配置如下:

<bean id="student" class="com.foooor.hellospring.pojo.Student">
    <property name="name" value="张三"/>

    <property name="hobbies">
        <!-- 配置数组类型的数据 -->
        <array>
            <value>篮球</value>
            <value>足球</value>
            <value>跑步</value>
        </array>
    </property>
</bean>
1
2
3
4
5
6
7
8
9
10
11
12
  • 使用 <array> 标签进行配置。

如果属性是对象类型的数组,也基本类似,不过 <array> 标签中需要使用 <ref> 标签:

举个栗子:

创建一个 Book.java 类:

public class Book {

    private String name;
    private String author;
  
    // ...getters and setters
}
1
2
3
4
5
6
7

然后在 Student 类中添加 Book[] 类型的属性:

public class Student {

    private String name;
  
    // 数组类型的数据
    private Book[] books;
  
    // ...getters and setters
  
}
1
2
3
4
5
6
7
8
9
10

那么在 bean.xml 中配置如下:

<!-- 配置book1对象 -->
    <bean id="book1" class="com.foooor.hellospring.pojo.Book">
        <property name="name" value="西游记"/>
        <property name="author" value="吴承恩"/>
    </bean>

    <!-- 配置book2对象 -->
    <bean id="book2" class="com.foooor.hellospring.pojo.Book">
        <property name="name" value="三国演义"/>
        <property name="author" value="罗贯中"/>
    </bean>

    <bean id="student" class="com.foooor.hellospring.pojo.Student">
        <property name="name" value="张三"/>

        <!-- 配置学生对象的books属性 -->
        <property name="books">
            <array>
                <ref bean="book1"/>
                <ref bean="book2"/>
            </array>
        </property>
    </bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • 使用 <array><ref> 标签进行配置。

# 5 List类型

List类型的配置和数组的基本一样,将 <array> 标签换成<list> 标签就可以了。

将 Student 的 hobbies 属性修改为 List<String>

public class Student {

    private String name;
    // 数组类型的数据
    private List<String> hobbies;
 
    // ...getters and setters
}
1
2
3
4
5
6
7
8

bean.xml 中配置,使用 <list> 标签即可:

<bean id="student" class="com.foooor.hellospring.pojo.Student">
    <property name="name" value="张三"/>

    <property name="hobbies">
        <!-- 配置list类型的数据 -->
        <list>
            <value>篮球</value>
            <value>足球</value>
            <value>跑步</value>
        </list>
    </property>
</bean>
1
2
3
4
5
6
7
8
9
10
11
12

同样,如果是对象类型的 List,和数组类型的配置也是一样的,将 <array> 标签换成<list> 标签即可,这里就不写了。

# 5 Set类型

Set 类型的数据和 List、数组也是类似的,将 <list> 标签换成 <set> 就可以了。

<bean id="student" class="com.foooor.hellospring.pojo.Student">
    <property name="name" value="张三"/>

    <property name="hobbies">
        <!-- 配置set类型的数据 -->
        <set>
            <value>篮球</value>
            <value>足球</value>
            <value>跑步</value>
        </set>
    </property>

    <property name="books">
        <!-- 配置set类型的数据 -->
        <set>
            <ref bean="book1"/>
            <ref bean="book2"/>
        </set>
    </property>

</bean>

<!-- 配置book1对象 -->
<bean id="book1" class="com.foooor.hellospring.pojo.Book">
    <property name="name" value="西游记"/>
    <property name="author" value="吴承恩"/>
</bean>

<!-- 配置book2对象 -->
<bean id="book2" class="com.foooor.hellospring.pojo.Book">
    <property name="name" value="三国演义"/>
    <property name="author" value="罗贯中"/>
</bean>
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
  • 所以数组、List、Set 三个的配置方式基本一致,只是使用的标签不同而已。

# 6 Map类型

首先在 Student 中创建 Map 类型的属性:

public class Student {

    private String name;

    // map 类型的数据,成绩
    private Map<String, Integer> scores;

    // map 类型的数据,书籍
    private Map<String, Book> books;
  
    // ...getters and setters
  
}
1
2
3
4
5
6
7
8
9
10
11
12
13

在 bean.xml 中配置如下:

<bean id="student" class="com.foooor.hellospring.pojo.Student">
    <property name="name" value="张三"/>

    <!-- 配置成绩, value 是 int 类型 -->
    <property name="scores">
        <map>
            <entry key="chinese" value="100"/>
            <entry key="math" value="90"/>
            <entry key="english" value="80"/>
        </map>
    </property>

    <!-- 配置书籍, value 是 Book 对象类型 -->
    <property name="books">
        <map>
            <entry key="xiyouji" value-ref="book1"/>
            <entry key="sanguoyanyi" value-ref="book2"/>
        </map>
    </property>
</bean>

<!-- 配置book1对象 -->
<bean id="book1" class="com.foooor.hellospring.pojo.Book">
    <property name="name" value="西游记"/>
    <property name="author" value="吴承恩"/>
</bean>

<!-- 配置book2对象 -->
<bean id="book2" class="com.foooor.hellospring.pojo.Book">
    <property name="name" value="三国演义"/>
    <property name="author" value="罗贯中"/>
</bean>
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
  • Map 类型使用 <map> 标签来配置,其中的元素使用 <entry> 标签来配置;
  • Map 中的 value,如果是基本数据类型的使用 value 属性配置,如果是对象类型,那么使用 value-ref 来配置。