概览 对于 Spring 框架来说,控制反转(Inversion of Control, IoC)和依赖注入(Dependency Injection, DI)是个等同的概念,控制反转是通过依赖注入实现的。在这篇文章中,我们会详细介绍 IoC 和 DI 的概念,然后我们会讨论 Spring 框架中是如何实现 IoC 和 DI 的。
什么是控制反转 IoC 是软件工程的一个设计原则,是指对象的依赖由框架来控制,而不是由对象的提供者来控制。策略设计模式 ,依赖注入(DI)等。本文我们主要介绍如何通过 DI 来实现控制反转。
什么是依赖注入 一般来说,每个 Java 应用程序都有几个对象,这些对象一起工作以提供系统的功能。当实现复杂的 Java 应用程序时,Java 对象之间应尽可能独立以增加这些对象的可复用性。依赖注入有助于把这些对象粘合在一起,同时保持它们的独立。
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 中。ApplicationContext的实现,包括ClassPathXmlApplicationContext,FileSystemXmlApplicationContext,以及WebApplicationContext。
Spring IoC 容器使用 xml 配置,或者注解配置来实现 Bean 的创建与注入。这些 xml 配置和注解配置,称为配置元数据,所谓元数据是指描述数据的数据。元数据本身不具备可执行的能力,只能通过外界代码来对这些元数据解析后进行一些有意义的操作。
可以使用下方法可以初始化一个 IoC 容器。
1 2 ApplicationContext  context  =                 new  ClassPathXmlApplicationContext ("Beans.xml" ); 
Spring 实现依赖注入 Spring 可以通过构造函数,设值函数,等方法来实现依赖注入。
基于构造函数的依赖注入 当容器调用带有一组参数的构造函数时,基于构造函数的 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.
 
基于设值函数的依赖注入 通过容器在 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.
 
小结 本文详细介绍了 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 实战,汪云飞著,电子工业出版社