从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

    • Spring 介绍
    • 程序中的耦合
    • IoC 的概念和作用
      • 获取依赖的方式
      • 准备环境
      • 调整项目
      • 添加依赖
      • 新建 bean.xml
      • 获取容器并创建对象
      • ApplicationContext 的三个常用实现类
      • ApplicationContext 和 BeanFactory 的区别
      • 三种创建 Bean 对象的方式
      • bean 的 scope 属性
      • bean 的生命周期
      • 源码
    • Spring 中的依赖注入
    • 基于注解的 IoC
    • 使用基于 XML 的 IoC 完成单表的 CRUD
    • 使用基于注解的 IoC 完成单表的 CRUD
    • IoC 的纯注解配置
    • Spring 整合 Junit
    • 事务问题
    • 代理模式
    • AOP 的概念和入门
    • 基于注解的 AOP
    • Spring 的 JdbcTemplate
    • JdBCDaoSupport
    • 基于 XML 的 AOP 实现事务控制
    • 基于注解的 AOP 实现事务控制
    • Spring 的事务控制
    • 基于 XML 的声明式事务控制
    • 基于注解的声明式事务控制
    • 纯注解实现事务控制
    • Spring 编程式事务控制
    • Spring5 新特性
  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • Spring
2023-05-08
目录

IoC 的概念和作用

# 20.IoC 的概念和作用

在了解了程序中的耦合后,我们就可以看看 Spring 是如何解决这个问题的,也就是 IoC。 ‍

# 获取依赖的方式

之前我们在获取对象时,都是采用 new 的方式。是主动去寻找依赖的:

‍

改为用工厂模式后,我们获取对象时,是跟工厂要资源,由工厂为我们查找或者创建对象,不再是主动的去寻找依赖,而是被动的提供

这种被动接收的方式获取对象的思想就是控制反转,它是 Spring 框架的核心之一。为什么叫控制反转呢?首先之前我们是使用 new 的方式来创建对象的,而改为使用工厂之后,就相当于将获取对象的主动权,交给了工厂,至于工厂返回的对象是否能用,我们是控制不了的,而是在配置文件配置的。

换句话说,控制权发生了转移,因此叫控制反转 Inverse Of Control,简称 IoC。IoC 能降低计算机程序的耦合。

如果说全部由我们自己实现 IoC,就太耗费精力了,我们可以使用将控制权交给 Spring。

特别说明:Spring5 版本是用 Java8 编写的,所以要求 Java8 及以上版本。 ‍

# 准备环境

之前我们提到了一个压缩包:LearnSpring/lib/spring-framework-5.0.2.RELEASE-dist.zip,这个 zip 包含了 Spring Framework 的所有 jar 包、文档和约束文件,解压后有 3 个文件夹:

  • docs 文件夹:文档

  • libs 文件夹:jar 文件

  • schema 文件夹:约束文件 ‍ 在 libs 目录下,每个依赖都有自己的文档和源码,例如:

  • spring-aop-5.0.2.RELEASE.jar

  • spring-aop-5.0.2.RELEASE-javadoc.jar

  • spring-aop-5.0.2.RELEASE-sources.jar

*.javadoc.jar 结尾的就是该依赖的文档,而 *.sources.jar 结尾的就是源码 ‍ 我们打开 spring-framework-5.0.2.RELEASE\docs\spring-framework-reference\index.html,可以看到 Spring Framework 的文档:

‍希望读者们可以将这个网页添加到收藏夹之类的地方,方便后续的阅读 ‍

# 调整项目

我们不再自己实现 IoC 了,因此 BeanFactory 可以删掉;同理,beans.properties 也删掉 ‍ 调整 service 层:

public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao = new AccountDaoImpl();
    @Override
    public void saveAccount() {
        accountDao.saveAccount();
    }
}
1
2
3
4
5
6
7
8

‍ 调整界面层 Client:

public class Client {
    public static void main(String[] args) {
        IAccountService as = new AccountServiceImpl();
        as.saveAccount();
    }
}
1
2
3
4
5
6

# 添加依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
1
2
3
4
5

‍ 我们可以看到多了 6 个依赖:

‍

第一个是 AOP,我们学到 AOP 的时候才用到;其他的例如 beans、context 都是要用到的

而最后一个 jcl,其实就是日志依赖 Jakarta Commons Logging,我们之前其实讲过:常见的日志框架-简单介绍 (opens new window)

‍

我们可以在 Maven 视图中,分析这个依赖的关系:

‍

可以看到如下结构:

‍

这个图是不是在我们介绍 Spring 时,用到的图很类似?

而 Core Container 就是我们说的容器 ‍

# 新建 bean.xml

我们新建一个配置文件,bean.xml,其实也可以叫其他名字,只要不包含中文;

然后我们导入约束,可以去 docs\spring-framework-reference\index.html 这里找:我们点击 core

‍

然后我们搜索 xmlns:可以看到有相关的约束,这里我们用第一个就行

‍

<?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">
  
</beans>
1
2
3
4
5
6
7

‍ 然后我们就可以配置全限定类名了,这里我们用 bean 标签:

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

    <!-- 把对象交给Spring来管理 -->
    <bean id="accountService" class="com.peterjxl.service.impl.AccountServiceImpl"/>
    <bean id="accountDao" class="com.peterjxl.dao.impl.AccountDaoImpl"/>
  
</beans>
1
2
3
4
5
6
7
8
9
10
11

bean 标签的内容,和我们之前 beans.properties 文件里的内容是一样的,id 和全限定类名。 ‍

# 获取容器并创建对象

接下来我们就是获取到容器,然后根据 id 获取里面的对象了。

在 Spring 中,我们并不会直接使用 BeanFactory,而是 ApplicationContext,这是一个接口,实现类有 ClassPathXmlApplicationContext,FileSystemXmlApplicationContext 和 AnnotationConfigApplicationContext,前面两个是基于配置文件的,后面是基于注解的,这里我们用第一个

‍

然后我们就通过容器来获取对象,改写 Client 的代码:

public static void main(String[] args) {
    // 1. 获取核心容器对象
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    // 2. 根据id获取Bean对象
    IAccountService as = (IAccountService)ac.getBean("accountService");
    IAccountDao adao = ac.getBean("accountDao", IAccountDao.class);

    System.out.println(as);
    System.out.println(adao);
}
1
2
3
4
5
6
7
8
9
10

‍ 首先我们创建了一个 ApplicationContext 对象,其参数就是配置文件的位置;

然后我们通过 getBean 方法获取了 IAccountService 对象,由于 getBean 返回的是 Object 对象,需要强转;

而如果我们获取对象时,传递了字节码对象,那么就不用强转也可以(第 6 行获取 IAccountDao 的时候) ‍

运行结果:可以看到正常创建了实现类

com.peterjxl.service.impl.AccountServiceImpl@46d56d67
com.peterjxl.dao.impl.AccountDaoImpl@d8355a8
1
2

# ApplicationContext 的三个常用实现类

说明:

  • ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(最常用)
  • FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
  • AnnotationConfigApplicationContext:它是用于读取注解创建容器的,我们后续会学习 ‍ 演示 FileSystemXmlApplicationContext:我们可以将配置文件放到桌面上,此时文件的路径为:C:\Users\peterjxl\Desktop\bean.xml
 public static void main(String[] args) {
        // 1. 获取核心容器对象
        ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\peterjxl\\Desktop\\bean.xml");
        // 2. 根据id获取Bean对象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        IAccountDao adao = ac.getBean("accountDao", IAccountDao.class);

        System.out.println(as);
        System.out.println(adao);
}
1
2
3
4
5
6
7
8
9
10

可以看到第二个实现类依赖具体的文件路径,可移植性不强,因此不推荐使用第二个。 ‍

# ApplicationContext 和 BeanFactory 的区别

之前我们使用 ApplicationContext 接口,获取了容器;其实也可以用 BeanFactory 接口

ApplicationContext:它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中的配置对象。我们可以在实现类的构造函数中,加个打印语句,即可验证。如果要用单例对象,采用此接口。

BeanFactory:它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据 id 获取对象了,什么时候才真正的创建对象。使用多例对象模式时,使用这个接口。
‍ 演示 ApplicationContext 立即加载,我们可以在构造函数中加个打印语句:

public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao = new AccountDaoImpl();
  
    public AccountServiceImpl() {
        System.out.println("对象创建了");
    }
    @Override
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13

运行结果:

对象创建了
com.peterjxl.service.impl.AccountServiceImpl@46d56d67
com.peterjxl.dao.impl.AccountDaoImpl@d8355a8
1
2
3

演示使用 BeanFactory:

Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
1
2

运行后,可以看到没有打印构造函数里的方法 ‍ 实际开发中,更多使用的是 ApplicationContext,因为 BeanFactory 是顶层接口,功能没那么完善;并且使用 ApplicationContext 还可以配置是使用单例模式还是多例模式,功能更完善

# 三种创建 Bean 对象的方式

之前我们都是创建我们自己项目中的 bean;实际开发中,经常会用到第三方依赖,要如何存放呢?此时就涉及到其他创建 Bean 对象的方式

‍

# 第一种方式

使用默认构造函数创建对象。在 Spring 的配置文件中使用 bean 标签,配以 id 和 class 属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建 bean 对象,此时如果类中没有默认构造函数,则对象无法创建。

<bean id="accountService" class="com.peterjxl.service.impl.AccountServiceImpl"/>
1

‍ 我们可以试着给默认函数加个参数:

public AccountServiceImpl(String name) {
    System.out.println("对象创建了");
}
1
2
3

此时运行会报错:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.peterjxl.service.impl.AccountServiceImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.peterjxl.service.impl.AccountServiceImpl.<init>()
1

可以看到其提示没有找到默认构造函数:No default constructor found

# 第二种方式

而实际开发中,我们经常会遇到没有默认构造函数的类,例如一些第三方的工具类;如果我们修改其源码,加上构造函数,也太麻烦了!此时我们可以调用其提供的构造方法,并使用 set 属性作为构造方法的函数(下一篇会讲怎么 set)。

除此之外,有时候我们并不是调用构造方法来获取对象,而是可能调用一个工厂类的方法,由该工厂类给我们返回对象。

我们可以新建一个类来模拟下:

package com.peterjxl.factory;
import com.peterjxl.service.IAccountService;

/**
 * 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
 */
public class InstanceFactory {
    public IAccountService getAccountService() {
        return new AccountServiceImpl("test");
    }
}
1
2
3
4
5
6
7
8
9
10
11

‍ 配置 bean.xml,指定工厂类和构造方法:

<bean id="instanceFactory" class="com.peterjxl.factory.InstanceFactory"/>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"/>
1
2

我们试着运行,可以看到是能正常获取对象的 ‍

# 第三种方式

有时候我们是调用工厂类中的静态方法来获取 bean。我们可以建立一个工厂类来模拟:

public class StaticFactory {
    public static IAccountService getAccountService() {
        return new AccountServiceImpl("test");
    }
}
1
2
3
4
5

配置 bean.xml:

<bean id="accountService" class="com.peterjxl.factory.StaticFactory" factory-method="getAccountService"/>
1

我们试着运行,也是可以看到能正常获取对象的 ‍

# bean 的 scope 属性

我们之前使用 ApplicationContext,默认都是单例模式的:

IAccountService as = (IAccountService)ac.getBean("accountService");
IAccountService as2 = (IAccountService)ac.getBean("accountService");
System.out.println("as == as2 ? :" + (as == as2));
1
2
3

运行结果:as == as2 ? :true ‍ 我们可以通过配置 bean 标签的 scope 属性,来进行调整。取值:

  • singleton:单例的(默认值)
  • prototype:多例的
  • request:作用于 Web 应用的请求范围
  • session:作用于 Web 应用的会话范围
  • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是 session,也就是为 prototype,即多例模式

常用的就是单例和多例模式。我们可以测试下,调整为多例模式:

 <bean id="accountService"
      class="com.peterjxl.factory.StaticFactory"
      factory-method="getAccountService"
      scope="prototype"
/>
1
2
3
4
5

运行结果:as == as2 ? :false ‍

# bean 的生命周期

‍

# 单例模式

对于单例对象而言,生命周期是这样的:

  • 出生:当容器创建时对象出生
  • 活着:只要容器在,对象一直活着
  • 死亡:容器销毁,对象消亡
  • 总结:单例对象的生命周期和容器相同

我们可以自定义初始化和销毁方法,当对象被初始化时,我们可以自定义对象的初始化方法;当对象被销毁时,我们可以自定义对象的销毁方法。 ‍ 我们可以演示下,在 AccountServiceImpl 里添加 init 和 destroy 方法:

public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao = new AccountDaoImpl();

    public AccountServiceImpl(String name) {
        System.out.println("对象创建了");
    }
    @Override
    public void saveAccount() {
        accountDao.saveAccount();
    }
    public void init() {
        System.out.println("对象初始化了");
    }

    public void destroy() {
        System.out.println("对象销毁了");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

‍ 然后配置 bean:

<bean id="accountService"
      class="com.peterjxl.factory.StaticFactory"
      factory-method="getAccountService"
      init-method="init"
      destroy-method="destroy"
/>
1
2
3
4
5
6

‍ 此时我们运行 Client,可以看到输出了“对象初始化了”,但没有输出“对象销毁了”;这是因为我们没有调用容器的销毁方法,就释放内存了;所以我们得手动关闭容器:注意要强制转换

((ClassPathXmlApplicationContext) ac).close();
1

然后再次运行,可以看到确实有销毁对象:

对象创建了
对象初始化了
as == as2 ? :true
com.peterjxl.service.impl.AccountServiceImpl@604ed9f0
com.peterjxl.dao.impl.AccountDaoImpl@6a4f787b
对象销毁了
1
2
3
4
5
6

‍

# 多例模式

对于多例对象而言,生命周期是这样的:

  • 出生:当我们使用对象时 Spring 框架为我们创建
  • 活着:对象只要是在使用过程中就一直活着
  • 死亡:当对象长时间不用,且没有别的对象引用时,由 Java 的垃圾回收器回收

# 源码

本项目已将源码上传到 GitHub (opens new window) 和 Gitee (opens new window) 上。并且创建了分支 demo2,读者可以通过切换分支来查看本文的示例代码。

上次更新: 2025/6/3 09:31:54
程序中的耦合
Spring 中的依赖注入

← 程序中的耦合 Spring 中的依赖注入→

最近更新
01
语雀文档一键下载至本地教程
07-04
02
要成功,就不要低估环境对你的影响
07-03
03
血泪教训:电子设备要定期开机
07-02
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式