深入理解 Spring 控制反转与依赖注入

2023-11-16

概览

对于 Spring 框架来说,控制反转(Inversion of Control, IoC)和依赖注入(Dependency Injection, DI)是个等同的概念,控制反转是通过依赖注入实现的。在这篇文章中,我们会详细介绍 IoC 和 DI 的概念,然后我们会讨论 Spring 框架中是如何实现 IoC 和 DI 的。

什么是控制反转

IoC 是软件工程的一个设计原则,是指对象的依赖由框架来控制,而不是由对象的提供者来控制。
IoC 可以通过多种机制来实现,例如,策略设计模式,依赖注入(DI)等。本文我们主要介绍如何通过 DI 来实现控制反转。

什么是依赖注入

一般来说,每个 Java 应用程序都有几个对象,这些对象一起工作以提供系统的功能。当实现复杂的 Java 应用程序时,Java 对象之间应尽可能独立以增加这些对象的可复用性。依赖注入有助于把这些对象粘合在一起,同时保持它们的独立。
依赖注入是 IoC 的一种实现方式,为理解依赖注入概念,我们先来看一下传统的对象依赖是如何实现的。
假设我们有一个包含文本编辑器的应用程序,文本编辑器提供拼写检查功能,传统的代码看起来是这样的:

public class TextEditor {
    private SpellChecker spellChecker;
    public TextEditor() {
        spellChecker = new SpellChecker();
    }
}

在这里,我们创建了TextEditorSpellChecker的依赖关系。如果使用了 IoC,代码可以写成这样子:

public class TextEditor {
   private SpellChecker spellChecker;
   public TextEditor(SpellChecker spellChecker) {
      this.spellChecker = spellChecker;
   }
}

TextEditor实例化时,通过在构造函数对实例变量spellChecker赋值,我们创建了TextEditorSpellChecker的依赖关系,这个依赖关系的建立是通过构造函数注入到TextEditor中的。

SpellChecker sc = new SpellChecker; // dependency
TextEditor textEditor = new TextEditor(sc);

上面我们采用类的使用者来实现依赖注入,对于 Spring 框架来说,依赖注入是由 Spring IoC 容器来实现的。

Spring IoC 容器

IoC 容器是 Spring 框架最基础的设施,对于 Spring 框架来说,接口ApplicationContext代表 IoC 容器。Spring IoC 容器负责创建 Bean(对象),通过容器将功能类 Bean 注入到你需要的 Bean 中。
Spring 框架提供了多个接口ApplicationContext的实现,包括ClassPathXmlApplicationContextFileSystemXmlApplicationContext,以及WebApplicationContext

Spring IoC 容器使用 xml 配置,或者注解配置来实现 Bean 的创建与注入。这些 xml 配置和注解配置,称为配置元数据,所谓元数据是指描述数据的数据。元数据本身不具备可执行的能力,只能通过外界代码来对这些元数据解析后进行一些有意义的操作。

可以使用下方法可以初始化一个 IoC 容器。

ApplicationContext context =
                new ClassPathXmlApplicationContext("Beans.xml");

Spring 实现依赖注入

Spring 可以通过构造函数,设值函数,等方法来实现依赖注入。

基于构造函数的依赖注入

当容器调用带有一组参数的构造函数时,基于构造函数的 DI 就完成了,其中每个参数代表一个对其他对象的依赖。
我们以一个完整的例子来说明 Spring IoC 容器是如何利用构造函数来实现 DI 的。

使用 IntelliJ IDEA 创建一个 Maven 项目,并添加以下依赖。

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>

添加SpellChecker.java

package me.leehao;

public class SpellChecker {
    public SpellChecker(){
        System.out.println("Inside SpellChecker constructor." );
    }
    public void checkSpelling() {
        System.out.println("Inside checkSpelling." );
    }
}

添加TextEditor.java

package me.leehao;

public class TextEditor {
    private SpellChecker spellChecker;
    public TextEditor(SpellChecker spellChecker) {
        System.out.println("Inside TextEditor constructor." );
        this.spellChecker = spellChecker;
    }
    public void spellCheck() {
        spellChecker.checkSpelling();
    }
}

添加程序入口类MainApp.java

package me.leehao;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("Beans.xml");
        TextEditor te = (TextEditor) context.getBean("textEditor");
        te.spellCheck();
    }
}

最后我们添加Beans.xml文件,我们使用 xml 配置的方式来实现 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-3.0.xsd">

    <!-- Definition for textEditor bean -->
    <bean id="textEditor" class="me.leehao.TextEditor">
        <constructor-arg ref="spellChecker"/>
    </bean>

    <!-- Definition for spellChecker bean -->
    <bean id="spellChecker" class="me.leehao.SpellChecker">
    </bean>
</beans>

<constructor-arg ref="spellChecker"/>表明textEditor对象通过构造函数来注入spellChecker的依赖。

运行程序,输出:

Inside SpellChecker constructor.
Inside TextEditor constructor.
Inside checkSpelling.

基于设值函数的依赖注入

通过容器在 Bean 上调用设值函数,也可以实现 Bean 的依赖注入。我们以例子来说明。

SpellChecker.java内容如下:

package me.leehao;

public class SpellChecker {
    public SpellChecker(){
        System.out.println("Inside SpellChecker constructor." );
    }
    public void checkSpelling() {
        System.out.println("Inside checkSpelling." );
    }
}

TextEditor.java的内容如下:

package me.leehao;

public class TextEditor {
    private SpellChecker spellChecker;
    public void setSpellChecker(SpellChecker spellChecker) {
        System.out.println("Inside setSpellChecker." );
        this.spellChecker = spellChecker;
    }
    public SpellChecker getSpellChecker() {
        return spellChecker;
    }
    public void spellCheck() {
        spellChecker.checkSpelling();
    }
}

TextEditor的定义跟上面的例子相比,多了一个setSpellChecker()设值函数。

程序入口MainApp.java的内容如下:

package me.leehao;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("Beans.xml");
        TextEditor te = (TextEditor) context.getBean("textEditor");
        te.spellCheck();
    }
}

Beans.xml的内容:

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

    <!-- Definition for textEditor bean -->
    <bean id="textEditor" class="me.leehao.TextEditor">
        <property name="spellChecker" ref="spellChecker"/>
    </bean>

    <!-- Definition for spellChecker bean -->
    <bean id="spellChecker" class="me.leehao.SpellChecker">
    </bean>
</beans>

在基于设值函数的 DI 中,我们改用了property元素。<property name="spellChecker" ref="spellChecker"/>将会调用类TextEditor的设值函数setSpellChecker(),并将 Bean spellChecker注入到 Bean textEditor

运行上面程序,输出:

Inside SpellChecker constructor.
Inside setSpellChecker.
Inside checkSpelling.

小结

本文详细介绍了 IoC 和 DI 的概念,并介绍了在 Spring 框架中是如何通过 DI 来实现 IoC 的,最后以例子说明如何基于构造函数和基于设值函数实现了依赖注入。

参考资料

  1. http://www.baeldung.com/inversion-control-and-dependency-injection-in-spring
  2. https://stackoverflow.com/questions/9403155/what-is-dependency-injection-and-inversion-of-control-in-spring-framework
  3. https://stackoverflow.com/questions/3058/what-is-inversion-of-control?noredirect=1&lq=1
  4. https://www.w3cschool.cn/wkspring/1h9m1h9m.html
  5. Java EE 开发的颠覆者 Spring Boot 实战,汪云飞著,电子工业出版社
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入理解 Spring 控制反转与依赖注入 的相关文章

随机推荐

  • python水仙花数

    题目 打印出所有的 水仙花数 所谓 水仙花数 是指一个三位数 其各位数字立方和等于该数本身 例如 153是一个 水仙花数 因为153 1的三次方 5的三次方 3的三次方 def narcissus narcissus number grou
  • 设计模式中的五大原则

    设计模式是一种思想 是智慧的结晶了 它有利于我们快速构建高效 模块化 高扩展性的代码 这种思想不仅仅在c 中有 c代码中也是有很充分的使用 设计模式是前提 是重中之重 那比它更重要的是什么 是原则 面向对象设计五大原则 1 单一职责原则 S
  • 爬虫用拨号好还是HTTP爬虫ip池好?

    程序员小伙伴们 在进行爬虫时 你是否曾纠结于选择拨号还是代理 不要犯愁 今天我将与你分享一些实用的择优技巧 帮助你在爬虫之路上实现更高效的提速 一 拨号和HTTP爬虫ip的优劣势分析 1 拨号 优势 拨号具有动态HTTP的特点 每次拨号连接
  • ajax加载aspx页面,如何使用jquery ajax显示来自aspx页面的响应

    我使用ajax jquery来请求aspx页面 并且此页面显示GidView 因此响应将是网格视图的html代码 并且我将响应添加到DIV以显示结果 当我在第一次发出请求时正常工作 但第二次没有从响应中添加任何内容 尽管存在要绑定的数据 如
  • git 仓库迁移

    git 仓库迁移 文章目录 git 仓库迁移 在目标服务器建立新的git 功能仓库 设置git 仓库源 上传代码 验证是否成功 git远程仓库地址查看 在目标服务器建立新的git 功能仓库 git VM 0 5 centos git ini
  • MySQL中的IF语句使用

    MySQL中的IF语句 在 MySQL 数据库中 IF 语句是一种常见的条件控制语句 它可以根据指定的条件返回不同的结果 在本文中 我们将介绍 IF 语句的基本用法以及实际应用场景 IF函数 MySQL 提供了 IF 函数来实现 IF 语句
  • xcode4的自动完成功能(Code sense or Code Snippet)

    社区会员rainbird分享 自动完成包括两种含义 一种是输入字母的时候可以动态弹出一个列表 然后通过选择 提高输入效率 这种好像叫代码提示 Code sense 另一种就是输入几个字母的时候一回车 出来一串儿字符 Code Snippet
  • 把一个对象 转为JSON格式的方法

    List
  • svn的使用手册

    svn的使用手册 svn的使用手册 svn介绍 安装svn 安装VisualSVN server 安装TortoiseSVN 安装EclipseSVN插件 使用SVN Eclipse下使用SVN 合并冲突 分支 svn的使用手册 svn介绍
  • SpringBoot 实现定时任务

    定时任务 一 使用背景 二 定时任务的优点 三 SpringBoot 实现定时任务 3 0 项目结构 3 1 pom xml 3 2 启动类 3 3 服务类 3 4 cron表达式 3 4 1 时间范围 3 4 2 特殊字符 3 4 3 c
  • 启明云端分享

    提示 启明云端从2013年起就作为Espressif 乐鑫科技 大中华区合作伙伴 我们不仅用心整理了你在开发过程中可能会遇到的问题以及快速上手的简明教程 同时也用心推出了基于乐鑫的相关应用方案 希望你能第一时间了解并快速用上好的方案和产品
  • 微信支付接口常用参数及证书区分

    注意 服务商模式下 均是使用服务商的以下信息 1 证书 1 1商户api证书 v2和v3接口都需要使用 1 1 1获取方式 什么是商户API证书 如何获取商户API证书 商户api证书 里面介绍了如何获取商户证书的详细步骤 1 1 2作用
  • MyCAT 连接MySQL 8 注意事项

    一 问题产生 MyCat是一个基于MySQL协议的开源的分布式中间件 其核心是分库分表 但是目前MyCat仍主要面对MySQL 5 5 5 6 5 7版 对最新的MySQL 8尚未完全支持 需要用户对MySQL 8和MyCat的配置进行一系
  • Unity3d之Socket UDP协议

    原文地址 http blog csdn net dingkun520wy article details 49201245 一 Socket 套接字 UDP协议的特点 1 是基于无连接的协议 没有生成连接的延迟所以速度比TCP快 2 支持一
  • linux系统如何进入屏保,linux上屏保设置

    linux下屏保设置 Linux文本终端 字符界面屏保取消 在我们日常使用Linux过程中 经常遇到使用屏幕终端一段时间后 显示器关 闭 屏幕上没有任何显示 一段时间后 屏幕就会关闭 无任何显示 若此时系统死机或僵死 而且屏幕上有输出 当遇
  • 如何用js替换文本里的换行符 \n?

    如何用js替换文本里的换行符 n 有下面一段文本 在编辑器里的格式如下 div line1line2line3 div 切换到浏览器 显示如下 line1line2line3 这里我想使浏览器显示效果变成如下形式 line1 line2 l
  • python 多线程示例

    python 多线程示例 import queue import time import threading import threading from datetime import datetime 创建一个线程安全的队列 q queu
  • Moveit简单使用,在rviz中实现手动拖动-记录

    GAZEBO下载 一 首先需要准备模型文件 可以是自己的solidworks用URDF工具导出的 也可以是在网上下载的URDF文件包 1 我用的是solidworks手动导出的模型 b站博主导出SOLIDWORKS模型至URDF这个教程比较
  • 报错解决:SyntaxError: Non-UTF-8 code starting with ‘\xe7‘

    今天抓取数据时使用re对数据进行提取时遇到的问题 syntaxError Non UTF 8 code starting with xe7 意思是有的中文字符无法转成utf 8的形式 如图所示 这个是因为抓取的数据中有的中文字符识别不了 相
  • 深入理解 Spring 控制反转与依赖注入

    概览 对于 Spring 框架来说 控制反转 Inversion of Control IoC 和依赖注入 Dependency Injection DI 是个等同的概念 控制反转是通过依赖注入实现的 在这篇文章中 我们会详细介绍 IoC