概览 对于 Spring 框架来说,控制反转(Inversion of Control, IoC)和依赖注入(Dependency Injection, DI)是个等同的概念,控制反转是通过依赖注入实现的。在这篇文章中,我们会详细介绍 IoC 和 DI 的概念,然后我们会讨论 Spring 框架中是如何实现 IoC 和 DI 的。
什么是控制反转 IoC 是软件工程的一个设计原则,是指对象的依赖由框架来控制,而不是由对象的提供者来控制。 IoC 可以通过多种机制来实现,例如,策略设计模式 ,依赖注入(DI)等。本文我们主要介绍如何通过 DI 来实现控制反转。
什么是依赖注入 一般来说,每个 Java 应用程序都有几个对象,这些对象一起工作以提供系统的功能。当实现复杂的 Java 应用程序时,Java 对象之间应尽可能独立以增加这些对象的可复用性。依赖注入有助于把这些对象粘合在一起,同时保持它们的独立。 依赖注入是 IoC 的一种实现方式,为理解依赖注入概念,我们先来看一下传统的对象依赖是如何实现的。 假设我们有一个包含文本编辑器的应用程序,文本编辑器提供拼写检查功能,传统的代码看起来是这样的:
1 2 3 4 5 6 public class TextEditor { private SpellChecker spellChecker; public TextEditor () { spellChecker = new SpellChecker (); } }
在这里,我们创建了TextEditor
与SpellChecker
的依赖关系。如果使用了 IoC,代码可以写成这样子:
1 2 3 4 5 6 public class TextEditor { private SpellChecker spellChecker; public TextEditor (SpellChecker spellChecker) { this .spellChecker = spellChecker; } }
在TextEditor
实例化时,通过在构造函数对实例变量spellChecker
赋值,我们创建了TextEditor
对SpellChecker
的依赖关系,这个依赖关系的建立是通过构造函数注入到TextEditor
中的。
1 2 SpellChecker sc = new SpellChecker ; TextEditor textEditor = new TextEditor (sc);
上面我们采用类的使用者来实现依赖注入,对于 Spring 框架来说,依赖注入是由 Spring IoC 容器来实现的。
Spring IoC 容器 IoC 容器是 Spring 框架最基础的设施,对于 Spring 框架来说,接口ApplicationContext
代表 IoC 容器。Spring IoC 容器负责创建 Bean(对象),通过容器将功能类 Bean 注入到你需要的 Bean 中。 Spring 框架提供了多个接口ApplicationContext
的实现,包括ClassPathXmlApplicationContext
,FileSystemXmlApplicationContext
,以及WebApplicationContext
。
Spring IoC 容器使用 xml 配置,或者注解配置来实现 Bean 的创建与注入。这些 xml 配置和注解配置,称为配置元数据,所谓元数据是指描述数据的数据。元数据本身不具备可执行的能力,只能通过外界代码来对这些元数据解析后进行一些有意义的操作。
可以使用下方法可以初始化一个 IoC 容器。
1 2 ApplicationContext context = new ClassPathXmlApplicationContext ("Beans.xml" );
Spring 实现依赖注入 Spring 可以通过构造函数,设值函数,等方法来实现依赖注入。
基于构造函数的依赖注入 当容器调用带有一组参数的构造函数时,基于构造函数的 DI 就完成了,其中每个参数代表一个对其他对象的依赖。 我们以一个完整的例子来说明 Spring IoC 容器是如何利用构造函数来实现 DI 的。
使用 IntelliJ IDEA 创建一个 Maven 项目,并添加以下依赖。
1 2 3 4 5 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.0.7.RELEASE</version > </dependency >
添加SpellChecker.java
:
1 2 3 4 5 6 7 8 9 10 11 package me.leehao;public class SpellChecker { public SpellChecker () { System.out.println("Inside SpellChecker constructor." ); } public void checkSpelling () { System.out.println("Inside checkSpelling." ); } }
添加TextEditor.java
:
1 2 3 4 5 6 7 8 9 10 11 12 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 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 的注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?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 ="textEditor" class ="me.leehao.TextEditor" > <constructor-arg ref ="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
内容如下:
1 2 3 4 5 6 7 8 9 10 package me.leehao;public class SpellChecker { public SpellChecker () { System.out.println("Inside SpellChecker constructor." ); } public void checkSpelling () { System.out.println("Inside checkSpelling." ); } }
TextEditor.java
的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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
的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 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
的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?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 ="textEditor" class ="me.leehao.TextEditor" > <property name ="spellChecker" ref ="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 的,最后以例子说明如何基于构造函数和基于设值函数实现了依赖注入。
参考资料
http://www.baeldung.com/inversion-control-and-dependency-injection-in-spring
https://stackoverflow.com/questions/9403155/what-is-dependency-injection-and-inversion-of-control-in-spring-framework
https://stackoverflow.com/questions/3058/what-is-inversion-of-control?noredirect=1&lq=1
https://www.w3cschool.cn/wkspring/1h9m1h9m.html
Java EE 开发的颠覆者 Spring Boot 实战,汪云飞著,电子工业出版社