Spring 控制反转(IOC)

在 Spring 中,程序员需要描述依赖关系,从而指导 Spring 进行依赖注入工作。

一、控制反转(IOC)

编程思想 控制反转(IOC)

二、Spring 的 IOC 实现

  • Spring 通过依赖注入(DI)的方式实现 IOC

  • 依赖:所谓依赖,就是一个对象所需的东西

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Student {
    private int ID;
    private Teacher teacher;

    public Student(int ID) {
    this.ID = ID;
    }

    public void setTeacher(Teacher teacher) {
    this.teacher = teacher;
    }
    }

    Student 对象依赖于 ID 和 teacher

  • 依赖注入:所谓依赖注入,就是由容器将依赖装配进对象之中

    1
    2
    3
    4
    5
    // 通过构造方法注入ID
    Student student = new Student(0001);

    // 通过setter方法注入teacher
    student.setTeacher(new Teacher);

三、需要做什么?

需要描述 Bean 的依赖关系,指导 Spring 进行依赖注入。

一共有三种方式:

  • 在 XML 文件中显式配置
    • 基于构造函数的依赖注入
    • 基于 Setter 的依赖注入
  • 自动依赖注入
  • 在 Java 中显式配置

四、基于构造函数的依赖注入

1. 说明

Spring 将会调用构造方法,并在构造方法中放入参数,从而实现依赖注入。

2. 注入值

(1) 依赖为基本类型

用 value 属性传递依赖值

1
2
3
<bean id="Bean名" class="全限定类名">
<constructor-arg value="依赖值" ··· />
</bean>

(2) 依赖为对象

首先配置依赖对象,然后用 ref 引用

1
2
3
4
5
6
7
<bean id="Bean名2" class="全限定类名2"/>

···

<bean id="Bean名" class="全限定类名">
<constructor-arg ref="Bean名2" ··· />
</bean>

3. 参数匹配

为了使 constructor-arg 标签与构造函数中的参数相匹配,往往需要显式指定匹配关系

(1) 不显式指定

如果依赖的类之间没有继承关联,则可以无需匹配,直接按顺序描述依赖即可。

1
2
3
4
<bean id="Bean名" class="全限定类名"/>
<constructor-arg ··· />
<constructor-arg ··· />
</bean>

实体类:

1
2
3
4
5
6
7
public class Student {
private String name;
private int ID;

// 构造器

}

配置文件:

1
2
3
4
<bean id="student" class="pojo.Student">
<constructor-arg value="小李"/>
<constructor-arg value="1001"/>
</bean>

测试结果:

(2) 指定下标

1
2
3
4
<bean id="Bean名" class="全限定类名">
<constructor-arg index="0" ··· />
<constructor-arg index="1" ··· />
</bean>

(3) 指定类型

1
2
3
4
<bean id="Bean名" class="全限定类名">
<constructor-arg type="全限定类名" ··· />
<constructor-arg type="全限定类名" ··· />
</bean>

(4) 指定参数名

1
2
3
4
<bean id="Bean名" class="全限定类名">
<constructor-arg name="依赖名" ··· />
<constructor-arg name="依赖名" ··· />
</bean>

4. c 命名空间

(1) 什么是 c 命名空间?

c 命名空间允许使用 bean 标签的属性(而不是子标签)来描述依赖

(2) 使用方式

  • 在 beans 标签中增加属性 xmlns:c="http://www.springframework.org/schema/c"

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    ···

    </beans>
  • 在 bean 标签中,用属性描述依赖,如下:

    1
    <bean id="Bean名" class="全限定类名" c:依赖名="依赖值" p:依赖名-ref="Bean名"/>

(3) 示例

普通方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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 name="classic" class="com.example.ExampleBean">
<constructor-arg name="email" value="[emailprotected]"/>
<constructor-arg name="spouse" ref="jane"/>
</bean>

<bean name="jane" class="com.example.Person">
<constructor-arg name="name" value="Jane Doe"/>
</bean>

</beans>

p 命名空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean name="p-namespace"
class="com.example.ExampleBean"
c:email="[emailprotected]"
c:spouse-ref="jane"/>

<bean name="jane"
class="com.example.Person"
c:name="Jane Doe"/>

</beans>

五、基于 Setter 的依赖注入

1. 说明

Spring 会首先实例化对象,然后通过对象的 setter 方法注入依赖。

2. 注入值

(1) 依赖为基本类型

用 value 属性传递依赖值

1
2
3
<bean id="Bean名" class="全限定类名">
<property name="依赖名" value="依赖值"/>
</bean>

(2) 依赖为对象

首先配置依赖对象,然后用 ref 引用

1
2
3
4
5
6
7
<bean id="Bean名2" class="全限定类名2">

···

<bean id="Bean名" class="全限定类名"/>
<property name="依赖名" ref="Bean名2"/>
</bean>

(3) 依赖为数组

1
2
3
4
5
6
7
8
9
<bean id="Bean名" class="全限定类名">
<property name="依赖名">
<array>
<value>元素1</value>
<value>元素2</value>
···
</array>
</property>
</bean>

(4) 依赖为 List

1
2
3
4
5
6
7
8
9
<bean id="Bean名" class="全限定类名">
<property name="依赖名">
<list>
<value>元素1</value>
<value>元素2</value>
···
</list>
</property>
</bean>

(5) 依赖为 Map

1
2
3
4
5
6
7
8
9
<bean id="Bean名" class="全限定类名">
<property name="依赖名">
<map>
<entry key="键1" value="值1"/>
<entry key="键2" value="值2"/>
···
</map>
</property>
</bean>

(6) 依赖为 Set

1
2
3
4
5
6
7
8
9
<bean id="Bean名" class="全限定类名">
<property name="依赖名">
<set>
<value>元素1</value>
<value>元素2</value>
···
</set>
</property>
</bean>

(7) 依赖为 Properties

1
2
3
4
5
6
7
8
9
<bean id="Bean名" class="全限定类名">
<property name="依赖名">
<props>
<prop key="属性名1">属性值1</prop>
<prop key="属性名2">属性值2</prop>
···
</props>
</property>
</bean>

3. 空值和 null

(1) 依赖为空值

将 value 属性值设为空字符串即可。

1
2
3
<bean id="Bean名" class="全限定类名">
<property name="依赖名" value=""/>
</bean>

(2) 依赖为 null

使用 null 标签。

1
2
3
4
5
<bean id="Bean名" class="全限定类名">
<property name="依赖名">
<null/>
</property>
</bean>

4. p 命名空间

(1) 什么是 p 命名空间?

p 命名空间允许使用 bean 标签的属性(而不是子标签)来描述依赖

(2) 使用方式

  • 在 beans 标签中增加属性 xmlns:p="http://www.springframework.org/schema/p"

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    ···

    </beans>
  • 在 bean 标签中,用属性描述依赖,如下:

    1
    <bean id="Bean名" class="全限定类名" p:依赖名="依赖值" p:依赖名-ref="Bean名"/>

(3) 示例

普通方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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 name="classic" class="com.example.ExampleBean">
<property name="email" value="[emailprotected]"/>
<property name="spouse" ref="jane"/>
</bean>

<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>

</beans>

p 命名空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean name="p-namespace"
class="com.example.ExampleBean"
p:email="[emailprotected]"
p:spouse-ref="jane"/>

<bean name="jane"
class="com.example.Person"
p:name="Jane Doe"/>

</beans>

六、自动依赖注入 - XML

1. 匹配方式

  • byName:根据 Bean 名 匹配依赖,自动依赖注入

    Bean名、对象参数名需要保持一致且唯一

  • byType:根据 Bean 的类型 匹配依赖,自动依赖注入

    Bean 类性、对象参数类型需要保持一致且唯一

  • constructor:类似于 byType,但依赖注入基于构造函数

2. 配置

1
<bean autowire="匹配方式" ··· />

七、自动依赖注入 - 注解

1. 使用方式

(1) 前置工作

  • 增加 context 命名空间:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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">

    ···

    </beans>
  • 开启注解功能:

    可以使用 <context:annotation-config><context:component-scan>,其中:

    • <context:component-scan> 用于为 Spring 中的 Bean 对应的类开启注解

      如果类使用注解,但 Spring 中并没有对应的 Bean,则注解不会生效

    • <context:component-scan> 除了 <context:component-scan> 的功能外,还会扫描指定包下的类,并根据注解注册对应的 Bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?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">

    <!--开启注解功能-->
    <context:annotation-config/>
    <!--开启注解功能,扫描包中的类,根据注解注册对应的Bean-->
    <context:component-scan base-package="全限定包名"/>

    </beans>

(2) 配置

使用注解配置自动依赖注入:

在类的属性或 setter 方法上,添加 @Autowired 注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class 类名 {
@Autowired
private 类名1 Xxx1;

private 类名2 Xxx2;

public void setXxx1(类名1 Xxx1) {
···
}

@Autowired
public void setXxx1(类名2 Xxx2) {
···
}
}

2. @Autowired

(1) 匹配方式

首先使用 byType 方式进行匹配,

当存在一个以上相同类型的 Bean 时,使用 byName 方式进行匹配

  • 实体类:

    1
    2
    3
    4
    5
    6
    public class Human {
    @Autowired
    private Cat cat;

    ···
    }

    xml:

    1
    2
    3
    4
    5
    <bean id="human" class="pojo.Human"/>

    <bean id="cat1" class="pojo.Cat"/>

    <bean id="dog" class="pojo.Dog"/>

    测试结果:

    说明:

    首先使用 byType 方式进行匹配,有 Bean 类型为 pojo.Cat,直接匹配成功(虽然名称不匹配)

  • 实体类:

    1
    2
    3
    4
    5
    6
    public class Human {
    @Autowired
    private Cat cat;

    ···
    }

    xml:

    1
    2
    3
    4
    5
    6
    7
    <bean id="human" class="pojo.Human"/>

    <bean id="cat1" class="pojo.Cat"/>

    <bean id="cat2" class="pojo.Cat"/>

    <bean id="dog" class="pojo.Dog"/>

    测试结果:

    说明:

    当存在一个以上相同类型的 Bean 时,使用 byName 方式进行匹配,由于没有 Bean 名与属性名相符,匹配失败

  • 实体类:

    1
    2
    3
    4
    5
    6
    public class Human {
    @Autowired
    private Cat cat;

    ···
    }

    xml:

    1
    2
    3
    4
    5
    6
    7
    <bean id="human" class="pojo.Human"/>

    <bean id="cat" class="pojo.Cat"/>

    <bean id="cat2" class="pojo.Cat"/>

    <bean id="dog" class="pojo.Dog"/>

    测试结果:

    说明:

    当存在一个以上相同类型的 Bean 时,使用 byName 方式进行匹配,有 Bean 名与属性名相符,匹配成功

(2) required

@Autowired 有一个名为 required 的属性

  • 当 required 为 true 时,该依赖必须被找到,否则报异常

  • 当 required 为 false 时,该依赖允许为 null,可以找不到

    1
    @Autowired(required = false)

(3) @Nullable

可以通过 @Nullable 来允许依赖为 null

(4) @Qualifier

可以通过 @Qualifier 指定希望匹配的 Bean 名

1
2
@Autowired
@Qualifier("Bean名")

(5) 注入集合

@Autowired 不仅支持注入单个 Bean,也支持注入集合。

如果在类型为集合的成员变量或其 Setter 上加 @Autowired 注解,则 Spring 会将所有 “与集合成员类型匹配”(包括实现类、子类) 的 Bean 注入到集合中。

Animal 接口:

1
2
3
4
5
public interface Animal {

void eat();

}

接口实现类 Cat:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class Cat implements Animal {

@Override
public void eat() {
System.out.println("cat eat");
}

@Override
public String toString() {
return "Cat{}";
}
}

接口实现类 Dog:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class Dog implements Animal {

@Override
public void eat() {
System.out.println("dog eat");
}

@Override
public String toString() {
return "Dog{}";
}
}

ZooService 类:

1
2
3
4
5
6
7
8
9
10
@Service
public class ZooService {

@Autowired
List<Animal> animals;

public List<Animal> getAnimals() {
return animals;
}
}

ZooService 类中有成员变量为 Animals 的集合,并在成员变量之上使用 @Autowired 注解。

测试代码:

1
2
3
4
5
6
7
8
9
10
@Autowired
ZooService zooService;

@Test
void test() {
List<Animal> animals = zooService.getAnimals();
for (Animal animal : animals) {
System.out.println(animal);
}
}

运行结果:

3. @Resource

(1) 匹配方式

首先使用 byName 方式进行匹配,

当没有同名 Bean 时,再使用 byType 方式进行匹配

  • 实体类:

    1
    2
    3
    4
    5
    6
    public class Human {
    @Resource
    private Cat cat;

    ···
    }

    xml:

    1
    2
    3
    4
    5
    <bean id="human" class="pojo.Human"/>

    <bean id="cat1" class="pojo.Cat"/>

    <bean id="cat" class="pojo.Dog"/>

    测试结果:

    说明:

    首先使用 byName 方式进行匹配,选择同名 Bean,又因类型不符而报错

  • 实体类:

    1
    2
    3
    4
    5
    6
    public class Human {
    @Resource
    private Cat cat;

    ···
    }

    xml:

    1
    2
    3
    4
    5
    <bean id="human" class="pojo.Human"/>

    <bean id="cat1" class="pojo.Cat"/>

    <bean id="dog" class="pojo.Dog"/>

    测试结果:

    说明:

    首先使用 byName 方式进行匹配,当没有同名 Bean 时,再使用 byType 方式进行匹配

(2) name

@Resource有一个名为 name 的属性,可以通过它来指定希望匹配的 Bean 名

参考