Spring框架(2) —— 控制反转(IoC)

简介

  • 本文分为四部分
    • 第一部分:介绍 内聚和耦合 的概念。
    • 第二部分:介绍Spring框架中的 IoC控制反转 机制。
    • 第三部分:介绍 ApplicationContext 和 BeanFactory 。
    • 第四部分:比较 ApplicationContext 和 BeanFactory 。

内聚和耦合

在介绍Spring IoC容器之前,我们先要了解一下软件设计好坏的评判标准:耦合和内聚。

耦合

  • 耦合性(Coupling)是对模块间关联程度的度量。
  • 耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差。

高耦合

  • 手机和充电线是高耦合,为什么呢?
  • 打个比方,苹果手机和安卓手机必须使用本机适配的充电线,而且在安卓手机中甚至还分为USB接口和Type-C接口。如果我使用苹果充电接口给安卓手机充电,那么苹果充电线无法正常输电,安卓手机也无法正常充电了。(忽略其他影响)
  • 由此可见,手机和充电接口之间的依赖关系紧密,独立性差,所以属于高耦合。

低耦合

  • 充电接口和充电接口是低耦合。
  • 打个比方,不论是苹果充电接口还是安卓充电接口,都可以使用苹果充电接头,也都可以使用安卓充电接头。(忽略其他影响)
  • 由此可见,充电接口和充电接头之间的依赖关系松散,独立性高,所以属于低耦合。

内聚

  • 内聚(Cohesion)是一个模块内部各成分间关联程度的度量。
  • 内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。
  • 软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。

低内聚

  • 职责混乱是低内聚。
  • 打个比方,开发人员除了开发项目,还要参与需求分析;测试人员除了测试项目,还要参与开发。这表示每一个内聚模块都没有做好自己分内的事,因为如果每个内聚模块都做好了自己该做的事情,别的模块是不会有插手的余地的。

高内聚

  • 各司其职是高内聚。
  • 打个比方,软件开发人员完成项目开发,测试人员完成项目测试。这表示每一个内聚模块都做好了自己分内的事情,不需要别的内聚模块来插手,也不需要去别的内裤模块插手。

控制反转 IoC(Inversion of Control)

  • 为什么要使用控制反转呢?
    • 我们在前面已经了解到“高内聚低耦合”是评判软件设计好坏的标准,以及“内聚”和“耦合”的定义。那么我们学习的 Spring 框架是否能帮助我们更好的设计出“高内聚低耦合”的软件呢?答案显然是肯定的。
    • 但是,Spring 框架使得程序“低耦合”的特点更加突出。因为在 Spring 框架中,由于控制反转 IoC(Inversion of Control)的机制,软件之间的依赖关系由 Spring 框架帮我们管理,这样做有效的降低了对象之间的耦合性
  • 控制反转是什么呢?
    • 控制反转 IoC(Inversion of Control)就是对象之间的依赖关系由容器来创建,对象之间的关系本来是由我们开发者自己创建和维护的,在我们使用Spring框架后,对象之间的关系由容器来创建和维护,将开发者做的事让容器做。
    • 换句话说,就是指new实例工作不再由程序员来做,而是交给Spring容器来做。new实例工作的控制权不再由程序员掌控。
      • 我们通过依赖注入,来实现控制反转。

依赖注入 DI(Dependency Injection)

  • Spring 最被认同的技术是控制反转的依赖注入,依赖注入是一种设计模式。
  • 控制反转(IoC)是一个通用的概念,它可以用许多不同的方式去表达,依赖注入仅仅是控制反转的一个具体的例子。
  • 当编写一个复杂的 Java 应用程序时,应用程序类应该尽可能的独立于其他的 Java 类来增加这些类可重用可能性,当进行单元测试时,可以使它们独立于其他类进行测试。依赖注入有助于将这些类粘合在一起,并且在同一时间让它们保持独立。
  • 到底什么是依赖注入?让我 们将这两个词分开来看一看。这里将依赖关系部分转化为两个类之间的关联。例如,类 A 依赖于类 B,表示类 B 将通过 IoC 被注入到类 A 中。
    • 依赖注入可以以向构造函数传递参数的方式发生,或者通过使用 setter 方法。

目录结构

  • src
    • main
      • java.cn.water
        • POJO.java(实体类)
      • resources
        • ApplicationContext
          • Beans.xml(Spring配置文件)
        • BeanFactory
          • Beans.xml(Spring配置文件)
    • test
      • java.cn.water.test
        • ApplicationContext
          • SpringTest.java(测试类)
        • BeanFactory
          • SpringTest.java(测试类)
  • pom.xml(Maven配置文件)

Maven配置文件

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>cn.water</groupId>
<artifactId>section01_Ioc</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<!-- Spring框架 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>compile</scope>
</dependency>
</dependencies>


</project>

实体类

POJO.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.water;

public class POJO {

/* 静态代码块 */
static{
System.out.println("POJO类被加载了!!!");
}

/* 成员变量 */
private String message;

/* Getter、Setter */
public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}

配置文件

ApplicationContext

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


<bean id="pojo" class="cn.water.POJO">
<property name="message" value="ApplicationContext:恭喜发财!"/>
</bean>


</beans>

BeanFactory

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


<bean id="pojo" class="cn.water.POJO">
<property name="message" value="BeanFactory:恭喜发财!"/>
</bean>


</beans>

测试类

ApplicationContext

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package cn.water.test.ApplicationContext;

import cn.water.POJO;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
* @author Water
* @date 2019/10/23 - 8:38
* @description ApplicationContext接口
* 1、ClassPathXmlApplicationContext实现类
* 2、FileSystemXmlApplicationContext实现类
* 3、立即加载(适用于单例模式)
*/
public class SpringTest {

/** 实现类:ClassPathXmlApplicationContext */
@Test
public void test01(){
/* 1、加载配置文件,初始化Bean对象 */
ApplicationContext app = new ClassPathXmlApplicationContext("ApplicationContext/Beans.xml");
/* 2、获取Bean对象 */
POJO pojo = app.getBean("pojo", POJO.class);
/* 3、调用方法 */
String message = pojo.getMessage();
/* 4、输出 */
System.out.println(message);

}


/** 实现类:FileSystemXmlApplicationContext */
@Test
public void test02(){
/* 1、加载配置文件,初始化Bean对象 */
ApplicationContext app = new FileSystemXmlApplicationContext("D:\\coding\\IDEASpace\\spring\\spring\\section01_Ioc\\src\\main\\resources\\ApplicationContext\\Beans.xml");
/* 2、获取Bean对象 */
POJO pojo = app.getBean("pojo", POJO.class);
/* 3、调用方法 */
String message = pojo.getMessage();
/* 4、输出 */
System.out.println(message);
}

/** 立即加载 */
@Test
public void test03(){
/* 1、加载配置文件,初始化Bean对象 */
ApplicationContext app = new ClassPathXmlApplicationContext("ApplicationContext/Beans.xml");
}




}

BeanFactory

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package cn.water.test.BeanFactory;

import cn.water.POJO;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
* @author Water
* @date 2019/10/23 - 8:39
* @description BeanFactory接口
* 1、XmlBeanFactory实现类
* 2、延迟加载(适用于多例模式)
*/
public class SpringTest {

/** XmlBeanFactory实现类 */
@Test
public void test01(){
/* 1、加载配置文件 */
ClassPathResource resource = new ClassPathResource("BeanFactory/Beans.xml");
/* 2、初始化Bean对象 */
BeanFactory factory = (BeanFactory) new XmlBeanFactory(resource);
/* 3、获取Bean对象 */
POJO pojo = (POJO) factory.getBean("pojo");
/* 4、执行方法 */
String message = pojo.getMessage();
/* 5、输出 */
System.out.println(message);

}


/** 延迟加载 */
@Test
public void test02(){
/* 1、加载配置文件 */
ClassPathResource resource = new ClassPathResource("BeanFactory/Beans.xml");
/* 2、初始化Bean对象 */
BeanFactory factory = (BeanFactory) new XmlBeanFactory(resource);
/* 3、获取Bean对象 */
POJO pojo = (POJO) factory.getBean("pojo"); /* 此时加载 */
}


}

BeanFactory 接口

  • 它是最简单的容器,给 DI 提供了基本的支持。
  • 它用org.springframework.beans.factory.BeanFactory 接口来定义。(beans)

目录结构

  • src
    • main
      • java.cn.water
        • POJO.java(实体类)
      • resources
        • Beans.xml(Spring配置文件)
    • test
      • java.cn.water.test
        • SpringTest.java(测试类)

实体类

POJO.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.water;

public class POJO {

/* 成员变量 */
private String message;

/* Getter、Setter */
public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}

配置文件

Beans.xml

  • bean 标签:表示一个Bean容器
    • id 属性:Bean容器的唯一标识符
    • class 属性:POJO类的全类名
  • property 标签:表示Bean容器的类成员
    • name 属性:Bean容器的类成员的唯一标识符
    • value 属性:Bean容器的类成员的
1
2
3
<bean id="pojo" class="cn.water.POJO">
<property name="message" value="BeanFactory:恭喜发财!"/>
</bean>

测试类

SpringTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test01(){
/* 1、加载配置文件 */
ClassPathResource resource = new ClassPathResource("BeanFactory/Beans.xml");
/* 2、初始化Bean对象 */
BeanFactory factory = (BeanFactory) new XmlBeanFactory(resource);
/* 3、获取Bean对象 */
POJO pojo = (POJO) factory.getBean("pojo");
/* 4、执行方法 */
String message = pojo.getMessage();
/* 5、输出 */
System.out.println(message);
}

输出结果

1
BeanFactory:恭喜发财!

ApplicationContext 接口

  • ApplicationContext 是 BeanFactory 的子接口,也被称为 Spring 上下文。
  • ApplicationContext 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。
  • ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。当然,BeanFactory 仍可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。
    • 由 org.springframework.context.ApplicationContext 接口定义。

目录结构

  • src
    • main
      • java.cn.water
        • POJO.java(实体类)
      • resources
        • Beans.xml(Spring配置文件)
    • test
      • java.cn.water.test
        • SpringTest.java(测试类)

实体类

POJO.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.water;

public class POJO {

/* 成员变量 */
private String message;

/* Getter、Setter */
public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}

配置文件

Beans.xml

1
2
3
<bean id="pojo" class="cn.water.POJO">
<property name="message" value="ApplicationContext:恭喜发财!"/>
</bean>

测试类

SpringTest.java

  • ClassPathXmlApplicationContext 实现类
    • 该容器从 XML 文件中加载已被定义的 bean。
      • 在这里,你需要提供 CLASSPATH 环境变量
        • ApplicationContext/Beans.xml
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test01(){
/* 1、加载配置文件,初始化Bean对象 */
ApplicationContext app = new ClassPathXmlApplicationContext("ApplicationContext/Beans.xml");
/* 2、获取Bean对象 */
POJO pojo = app.getBean("pojo", POJO.class);
/* 3、调用方法 */
String message = pojo.getMessage();
/* 4、输出 */
System.out.println(message);

}
  • FileSystemXmlApplicationContext 实现类
    • 该容器从 XML 文件中加载已被定义的 bean。
      • 在这里,你需要提供给构造器 XML 文件的完整路径
        • C:\\src\\main\\resources\\ApplicationContext\\Bean.xml
        • C:/src/main/resources/ApplicationContext/Bean.xml
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          @Test
          public void test02(){
          /* 1、加载配置文件,初始化Bean对象 */
          ApplicationContext app = new FileSystemXmlApplicationContext("D:\\coding\\IDEASpace\\spring\\spring\\section01_Ioc\\src\\main\\resources\\ApplicationContext\\Beans.xml");
          /* 2、获取Bean对象 */
          POJO pojo = app.getBean("pojo", POJO.class);
          /* 3、调用方法 */
          String message = pojo.getMessage();
          /* 4、输出 */
          System.out.println(message);
          }

输出结果

1
ApplicationContext:恭喜发财!

区别

  • ApplicationContext
    • 在构建核心容器时,创建对象采用 立即加载 的方式。一读取完配置文件,立马创建配置的对象。
    • 由于只在读取配置文件时创建对象,所以 适用于单例模式
  • BeanFactory
    • 在构建核心容器时,创建对象采用 延迟加载 的方式。什么时候根据id获取对象了,什么时候创建配置的对象。
    • 由于每次获取对象时,都会创建对象,所以 适用于多例模式

ApplicationContext

实体类

POJO.java

  • 加入 静态代码块,用于识别 POJO类 是何时被加载的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.water;

public class POJO {

/* 静态代码块 */
static{
System.out.println("POJO类被加载了!!!");
}

/* 成员变量 */
private String message;

/* Getter、Setter */
public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}

配置文件

Beans.xml

1
<bean id="pojo" class="cn.water.POJO"></bean>

测试类

SpringTest.java

1
2
3
4
5
@Test
public void test03(){
/* 1、加载配置文件,初始化Bean对象 */
ApplicationContext app = new ClassPathXmlApplicationContext("ApplicationContext/Beans.xml");
}

输出结果

1
POJO类被加载了!!!

BeanFactory

实体类

POJO.java

  • 加入 静态代码块,用于识别 POJO类 是何时被加载的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.water;

public class POJO {

/* 静态代码块 */
static{
System.out.println("POJO类被加载了!!!");
}

/* 成员变量 */
private String message;

/* Getter、Setter */
public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}

Spring配置文件

Beans.xml

1
<bean id="pojo" class="cn.water.POJO"></bean>

测试类

SpringTest.java

1
2
3
4
5
6
7
8
9
@Test
public void test02(){
/* 1、加载配置文件 */
ClassPathResource resource = new ClassPathResource("BeanFactory/Beans.xml");
/* 2、初始化Bean对象 */
BeanFactory factory = (BeanFactory) new XmlBeanFactory(resource);
/* 3、获取Bean对象 */
POJO pojo = (POJO) factory.getBean("pojo"); /* 此时加载 */
}

输出结果

1
POJO类被加载了!!!
-------------本文结束-------------
Donate comment here