`

Spring控制反转/依赖注入(转)

 
阅读更多
Spring控制反转/依赖注入
看了n篇文章也不明白控制反转到底是在说什么,今天终于看到一个像样的解释。以下内容选自于《Spring从入门到精通》作者:郭锋 清华大学出版社     出版时间:2006年10月      引自:CSDN读书频道 地址:http://book.csdn.net/bookfiles/250/index.html(本人帖在这只是为了学习方便,希望不涉及侵权问题)

在第2章中,笔者通过两个简单的实例展示了Spring的IoC功能,接下来将对Spring的IoC进行详细的讲解,因为Spring的核心就是IoC。在本章中,首先从IoC的基本思想开始,然后通过实例的方式使读者对其概念和工作原理有一个深入的了解,最后会把第2章中的第一个实例进行改编,使其通过构造方式来实现同样的功能。

3.1     反向控制/依赖注入
近年来,在Java社区中掀起了一股轻量级容器的热潮,几乎每隔一段时间,就会有新的轻量级容器出现,这些轻量级的容器能够较好地帮助开发者快速地将不同的组件组装成一个应用程序。在这些轻量级的容器的背后,有一个共同的模式决定着容器装配组件的方式,就是“反向控制”,即IoC,英文全称是Inversion of Control。Martin
Fowler深入地探索了“反向控制”的工作原理,并为其起了一个新的名字叫做“依赖注入”,即DI,英文全称是Dependency Injection。关于Martin Fowler的这篇文章,读者可以在其网站上看到,网址是http://www.martinfowler.com/articles/injection.html。
3.1.1     反向控制(IoC)
单从字面上,其实很难理解“反向控制”所要表达的含义。其实在编程时,开发人员常说的“实现必须依赖抽象,而不是抽象依赖实现”就是“反向控制”的一种表现方式。下面,笔者主要通过举例来说明这个抽象的概念。这个实例主要说明的是如何通过IoC来实现业务逻辑从哪种数据库中取数据的问题。可能的取数据方式有3种,分别是:
     ●          从SQL Server数据库中取数据。
     ●          从DB2数据库中取数据。
     ●          从Oracle数据库中取数据。
介绍这个实例的思路是:首先介绍编写这类程序通常的做法,然后指出这种做法的不足,接着给出一种比较好的做法,即通过IoC来实现这类功能,最后对这种做法进行总结,使读者一步一步地了解IoC。编写这类程序通常做法的具体步骤如下:
(1)通常编写这类程序都是先编写一个从数据库取数据的类SqlServerDataBase.java,这里以从SQL
Server数据库中取数据为例。SqlServerDataBase.java的示例代码如下。其中getDataFromSqlServer()是SqlServerDataBase类中的一个方法,具体负责从SQL Server数据库中取数据。
//******* SqlServerDataBase.java**************
public class SqlServerDataBase {
            ……
       //从SQLServer数据库中获取数据
            public List getData() {
                      ……
            }
}

(2)业务逻辑类Business.java通过SqlServerDataBase.java中的方法来从SQL
Server数据库中取数据。Business.java的示例代码如下。其中SqlServerDataBase是用来从SQL Server数据库中取数据的类。
//******* Business.java**************
public class Business {
            private SqlServerDataBase db = new SqlServerDataBase();
            ……
       //从SQL Server数据库中获取数据
            public void getData() {
                      ……
       List list = db.getDataFromSqlServer();
       ……
            }
}

可以看出以上程序编写的不足之处:Business类依赖于SqlServerDataBase类,如果业务改变,用户现在要求从DB2或Oracle数据库中取数据,则这个程序就不适用了,必须要修改Business类。
(3)改为从DB2数据库中取数据,DB2DataBase.java的示例代码如下。其中getDataFrom
DB2()是DB2DataBase类中的一个方法,具体负责从DB2数据库中取数据。
//******* DB2DataBase.java**************
public class DB2DataBase {
            ……
            //从DB2数据库中获取数据
            public List getData() {
                      ……
            }
}

(4)必须修改业务逻辑类Business.java,改为从DB2数据库中取数据。Business.java的示例代码如下,其中DB2DataBase是用来从DB2数据库中取数据的类。
//******* Business.java**************
public class Business {
            private DB2DataBase db = new DB2DataBase();
            ……
            //从DB2数据库中获取数据
            public void getData() {
                      ……
                      List list = db.getDataFromDB2();
                      ……
            }
}

(5)同样,用户现在要求从Oracle数据库中取数据,则这个程序就不适用了。改为从Oracle数据库中取数据,OracleDataBase.java的示例代码如下。其中getDataFromOracle
()是OracleDataBase类中的一个方法,具体负责从Oracle数据库中取数据。
//******* OracleDataBase.java**************
public class OracleDataBase {
            ……
            //从Oracle数据库中获取数据
            public List getData() {
                      ……
            }
}

(6)还要修改业务逻辑类Business.java,改为从Oracle数据库中取数据。Business.java的示例代码如下。其中OracleDataBase是用来从Oracle数据库中取数据的类。
//******* Business.java**************
public class Business {
            private OracleDataBase db = new OracleDataBase();
            ……
            //从Oracle数据库中获取数据
            public void getData() {
                      ……
                      List list = db.getDataFromOracle ();
                      ……
            }
}

至此,读者应该可以发现了,这可不是一个好的设计,因为每次业务需求的变动都要导致程序的大量修改,怎样才能改变这种情形的发生呢?怎样才能实现Business类的重用呢?IoC就可以解决这个问题,它可以通过面向抽象编程来改变这种情况。
下面就利用IoC来实现Business类的重用,编写思路是:首先编写一个获取数据的接口,然后每个具体负责从各种数据库中获取数据的类都实现这个接口,而在业务逻辑类中,则根据接口编程,并不与具体获取数据的类打交道。通过IoC来实现这个功能的具体步骤如下。
(1)编写用来获取数据的接口DataBase。DataBase.java的示例代码如下:
//******* DataBase.java**************
public interface DataBase {
            //该方法用来获取数据
            public void getData();
}

(2)编写具体负责从SQL
Server数据库中取数据的类SqlServerDataBase,该类实现了接口DataBase。
SqlServerDataBase.java的示例代码如下:
//******* SqlServerDataBase.java**************
public class SqlServerDataBase implement DataBase {
            //该方法用来获取数据
            public void getData() {
                      //以下是具体从SQL Server数据库中取数据的代码
                      ……
            }
}

(3)编写业务逻辑类Business,该类只针对接口DataBase编码,而不针对实体类。
Business.java的示例代码如下:
//******* Business.java**************
public class Business {
            //针对接口DataBase定义变量
            private DataBase db;
            public void setDataBase(DataBase db) {
                      this.db = db;
            }
            ……
            //根据注入的数据库类,从×××数据库中获取数据
            public void getData() {
                      ……
                      db.getData();
                      ……
            }
}

(4)编写测试类TestBusiness。TestBusiness.java的示例代码如下:
//******* TestBusiness.java**************
public class TestBusiness {
            private Business business = new Business();
            ……
            //根据注入的数据库类,从SQL Server数据库中获取数据
            public void getData() {
                      ……
                      business. setDataBase(new SqlServerDataBase());
                      business.getData();
                      ……
            }
}

通过这种方式Business类就可以重用了,不管从哪种数据库中获取数据,Business类都不用改动,只需要实现具体的DataBase接口就可以了。例如,用户要求改为从DB2数据库中获取数据,只要实现一个具体负责从DB2数据库中取数据的类就可以了。
(5)编写具体负责从DB2数据库中取数据的类DB2DataBase,该类实现了接口DataBase。DB2DataBase.java的示例代码如下:
//******* DB2DataBase.java**************
public class DB2DataBase implement DataBase {
            public void getData() {
                      //以下是具体从DB2数据库中取数据的代码
                      ……
            }
}

(6)业务逻辑类Business不用作任何改动,修改测试类TestBusiness。TestBusiness.java的示例代码如下:
//******* TestBusiness.java**************
public class TestBusiness {
            private Business business = new Business();
            ……
            //根据注入的数据库类,从DB2数据库中获取数据
            public void getData() {
                      ……
                      business. setDataBase(new DB2DataBase());
                      business.getData();
                      ……
            }
}

(7)如果用户又要求改为从Oracle数据库中获取数据,只要实现一个具体负责从Oracle数据库中取数据的类就可以了。编写具体负责从Oracle数据库中取数据的类OracleDataBase,该类实现了接口DataBase。OracleDataBase.java的示例代码如下:
//******* OracleDataBase.java**************
public class OracleDataBase implement DataBase {
            public void getData() {
                      //以下是具体从Oracle数据库中取数据的代码
                      ……
            }
}

(8)业务逻辑类Business不用作任何改动,修改测试类TestBusiness。TestBusiness.java的示例代码如下:
//******* TestBusiness.java**************
public class TestBusiness {
            private Business business = new Business();
            ……
            //根据注入的数据库类,从Oracle数据库中获取数据
            public void getData() {
                      ……
                      business. setDataBase(new OracleDataBase());
                      business.getData();
                      ……
            }
}

从上面的例子可以看到,在第一个例子中,使用通常的做法,Business类依赖于具体获取数据的类;而在第二个例子中,通过接口来编程,即控制关系的反向转移,实现了IoC功能,并使代码获得了重用。这也就实现了上面所说的“实现必须依赖抽象,而不是抽象依赖实现”。

3.1.2     依赖注入(DI)
Martin Fowler在其文章中提出了“它们反转了哪方面的控制”的问题后,就为IoC起了一个更能说明这种模式特点的新名字,叫做“依赖注入”,即Dependency Injection,Spring就是使用Dependency Injection来实现IoC功能,接着Martin Fowler介绍了Dependency Injection的3种实现方式,接下来笔者将结合上面的例子对这3种实现方式进行详细的讲解。

3.2     依赖注入的3种实现方式
在讲解依赖注入的3种实现方式之前,这里先澄清一下依赖注入的意义:让组件依赖于抽象,当组件要与其他实际对象发生依赖关系时,通过抽象来注入依赖的实际对象。
依赖注入的3种实现方式分别是:接口注入(interface injection)、Set注入(setter
injection)和构造注入(constructor injection)。接下来笔者还将主要通过举例的方式,把依赖注入的3种实现方式介绍给读者。

3.2.1     接口注入(interface injection)
接口注入指的就是在接口中定义要注入的信息,并通过接口完成注入。结合前面的示例,其具体步骤如下。
(1)编写一个接口IBusiness,各种数据库的注入将通过这个接口进行。IBusiness.java的示例代码如下:
//******* IBusiness.java**************
public interface IBusiness {
            public void createDI(DataBase db);
}

(2)任何想要使用数据库实例的类都必须实现这个接口,业务逻辑类Business实现这个接口IBusiness。Business.java的示例代码如下:
//******* Business.java**************
public class Business implement IBusiness {
            private DataBase db;
            public void createDI (DataBase db) {
                      this.db = db;
            }
            ……
            //根据注入的数据库类,从×××数据库中获取数据
            public void getData() {
                      ……
                      db.getData();
                      ……
            }
}

(3)编写测试类TestBusiness。TestBusiness.java的示例代码如下:
//******* TestBusiness.java**************
public class TestBusiness {
            private Business business = new Business();
            ……
            //根据注入的数据库类,从Oracle数据库中获取数据
            public void getData() {
                      ……
                      business. createDI (new OracleDataBase());
                      business.getData();
                      ……
            }
}

如果要完成依赖关系注入的对象,必须实现IBusiness接口。
3.2.2     Set注入(setter injection)
Set注入指的就是在接受注入的类中定义一个Set方法,并在参数中定义需要注入的元素。为了让类Business接受DataBase的注入,需要为它定义一个Set方法来接受DataBase的注入。Business.java的示例代码如下:
//******* Business.java**************
public class Business {
            private DataBase db;
            public void setDataBase(DataBase db) {
                      this.db = db;
            }
            ……
            //根据注入的数据库类,从×××数据库中获取数据
            public void getData() {
                      ……
                      db.getData();
                      ……
            }
}

更详细的代码,可以参看3.1节的第二个例子,采用的就是Set注入的方式。
3.2.3     构造注入(constructor injection)
构造注入指的就是在接受注入的类中定义一个构造方法,并在参数中定义需要注入的元素。为了让类Business接受DataBase的注入,需要为它定义一个构造方法,来接受DataBase的注入。Business.java的示例代码如下:
//******* Business.java**************
public class Business {
            private DataBase db;
            public Business (DataBase db) {
                      this.db = db;
            }
            ……
            //根据注入的数据库类,从×××数据库中获取数据
            public void getData() {
                      ……
                      db.getData();
                      ……
            }
}


3.3     将HelloWorld实例改为构造注入方式实现
Spring支持Set注入(setter injection)和构造注入(constructor
injection),但更推荐使用Set注入。上面讲过,第2章的第一个实现HelloWorld的实例就是采用Set注入方式实现的,读者可以参看第2章的实例。下面笔者把这个实例改为采用构造注入方式实现。改写思路是:首先修改类HelloWorld,在该类中增加一个构造方法,然后修改Spring的配置文档config.xml,最后编写测试程序TestHelloWorld.java。

3.3.1     修改HelloWorld.java
修改com.gc.action包下的HelloWorld.java,增加一个构造方法,并把要注入的字符串msg作为参数,代码如下,在HelloWorld类中增加了一个构造方法public
HelloWorld (String msg)。
//*******HelloWorld.java**************
package com.gc.action;
public class HelloWorld {
            //该变量用来存储字符串
            public String msg = null;
            //增加了一个构造方法
            public HelloWorld (String msg) {
                      this.msg = msg;
            }        
            //设定变量msg的set方法
            public void setMsg(String msg) {
                      this.msg = msg;
            }
            //获取变量msg的get方法
            public String getMsg() {
                      return this.msg;
            }
}


3.3.2     修改config.xml
在Spring中,利用Set注入和构造注入时,在XML配置文档中使用的语法是不一样的。修改配置文件config.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!--定义一个Bean,通过构造函数进行注入-->
     <bean id="HelloWorld" class="com.gc.action.HelloWorld">
         <!--通过构造函数进行注入-->
         <constructor-arg index="0">
             <value>HelloWorld</value>
         </constructor-arg>
     </bean>
</beans>

代码说明:
     ●          constructor-arg,用来表示是通过构造方式来注入参数的。
     ●          index="0",表示是构造方法中的第一个参数,如果只有一个参数,则可以不用设置这个属性值。

3.3.3     编写测试程序TestHelloWorld.java
修改TestHelloWorld.java,代码如下:
//******* TestHelloWorld.java**************
package com.gc.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import com.gc.action.HelloWorld;
public class TestHelloWorld {
            public static void main(String[ ] args) { 
                 //通过ApplicationContext来获取Spring的配置文件
                      ApplicationContext actx=new 
FileSystemXmlApplicationContext("config.xml");
                      HelloWorld HelloWorld = (HelloWorld) 
actx.getBean("HelloWorld");
                      System.out.println(HelloWorld.getMsg());
       } 
}


3.3.4     运行测试程序并查看输出结果
在Eclipse中运行Java程序的步骤如下:
(1)确保当前在Eclipse中编辑的是TestHelloWorld.java文件。
(2)选择Run→Run As→Java Application命令,Eclipse即可运行TestHelloWorld.java。
(3)输出结果为“HelloWorld”,如图3.1所示。

图3.1     输出结果为“HelloWorld”

3.4     使用哪种注入方式
至于要使用构造注入或Set注入来完成依赖注入这个问题,其实就是在讨论:要在对象建立时就准备好所有的资源,或是在对象建立好后,使用Set注入来进行设定。
因为网络上关于这个的讨论太多了,各有各的道理,所以在这里不想再进行讨论,只是将笔者在实际工作中的一些经验分享给读者。
使用构造注入可以在建构对象的同时一并完成依赖关系的建立,对象一建立则所有的一切也就准备好了,但如果要建立的对象关系很多,使用构造注入会在建构函式上留下一长串的参数,且不易记忆,这时使用Set注入会是个不错的选择。
使用Set注入可以有明确的名称,可以了解注入的对象会是什么,像setXXX()这样的名称会比记忆Constructor上某个参数位置代表某个对象更好。
然而使用Set注入由于提供了setXXX()方法,所以不能保证相关的数据成员或资源在执行时不会被更改设定,所以如果开发人员想要让一些数据成员或资源变为只读或是私有,使用构造注入会是个简单的选择。
3.5     小       结
Spring的核心是个IoC容器,用户可以用Setter或Constructor的方式来实现自己的业务对象。至于对象与对象之间的关系建立,则通过组态设定,让Spring在执行时根据组态的设定来建立对象之间的依赖关系,开发人员就不必特地撰写一些Helper来自行建立这些对象之间的依赖关系,这不仅减少了大量的程序撰写,也降低了对象之间的耦合程度。当读者了解了IoC的工作原理和一些基本使用方法后,也就对Spring的核心有了一定的认识,接下来从第4章开始笔者将详细讲解Spring的语法。
分享到:
评论

相关推荐

    spring 控制反转和依赖注入.docx

    Spring 控制反转和依赖注入基础知识详解 Spring 框架是 Java 企业级开发的轻量级开发框架,于 2003 年创建,主要用于解决企业级开发的复杂性。其主要优势在于分层架构,允许在不同层级中使用一个组件(其他框架)...

    Spring 控制反转 依赖注入

    **Spring 框架中的控制反转 (IoC) 和依赖注入 (DI)** 在软件开发中,控制反转(Inversion of Control,简称IoC)是一种设计原则,它将对象的创建和管理权从代码中剥离出来,转交给一个外部容器(如Spring框架)。...

    Spring 控制反转和依赖注入

    Spring 控制反转和依赖注入

    springIOC控制反转 依赖注入实例

    Spring IOC(Inversion of Control,控制反转)是Spring框架的核心特性,它改变了传统Java应用程序中对象的创建和管理方式。在传统的程序设计中,我们通常手动创建对象并管理它们之间的依赖关系,而在Spring中,这些...

    自定义spring控制反转(依赖注入)

    在Spring框架中,控制反转(IoC,Inversion of Control)和依赖注入(DI,Dependency Injection)是核心设计理念,它们极大地简化了Java应用的开发和维护。本文将深入探讨如何自定义Spring实现读取`...

    spring的控制反转和依赖注入详解

    spring的控制反转和依赖注入详解

    自己实现的Spring反转控制和依赖注入

    Spring框架是Java开发中广泛应用的一个开源框架,以其优秀的反转控制(Inversion of Control,IoC)和依赖注入(Dependency Injection,DI)理念为核心,极大地简化了企业级应用的开发工作。下面将详细介绍这两个...

    Spring_01_入门篇_依赖注入(控制反转)_ANN

    Spring的核心特性之一是依赖注入(Dependency Injection,简称DI),也称为控制反转(Inversion of Control,简称IoC)。 **依赖注入 (DI) 和 控制反转 (IoC)** 依赖注入是Spring的核心设计原则之一。它是一种设计...

    Martin Fowler 控制反转与依赖注入

    ### Martin Fowler 控制反转与依赖注入 #### 重要概念与背景 Martin Fowler的文章探讨了Java社区近期关注的一个热点话题:轻量级容器及其背后的模式。这些容器的主要目标是帮助开发者将来自不同项目的组件组装成一...

    Spring 核心机制(依赖注入)

    在Spring框架中,依赖注入和控制反转实际上是同一概念的不同表达形式。当程序中的组件需要另一个组件的帮助时,通常是由调用者(即需要帮助的角色)来创建被调用者(即提供帮助的角色)的实例。但在Spring中,这个...

    依赖倒置+控制反转+依赖注入+面向接口编程

    在软件设计领域,依赖倒置、控制反转、依赖注入以及面向接口编程是四个非常重要的概念,它们都是现代软件开发中的核心原则,特别是对于构建可扩展、可维护的系统至关重要。 首先,我们来深入理解一下“依赖倒置”...

    spring 控制反转的模拟程序

    Spring框架是Java开发中最常用的轻量级框架之一,它的核心特性是依赖注入(Dependency Injection,简称DI),也常被称为控制反转(Inversion of Control,简称IoC)。控制反转是一种设计模式,它改变了传统应用程序...

    Spring Ioc 注解 依赖注入

    ### Spring IoC与注解依赖注入详解 #### 一、Spring框架简介 Spring框架是由Rod Johnson创建的一个开源项目,最初是为了解决企业级应用开发中的复杂性问题而诞生的。Spring框架的核心特性包括IoC(Inversion of ...

    19丨理论五:控制反转、依赖反转、依赖注入,这三者有何区别和联系?1

    依赖反转、控制反转和依赖注入是面向对象编程中重要的设计原则和实践,它们在软件设计中扮演着关键角色,尤其在Spring框架中被广泛应用。现在,让我们深入理解这三个概念的区别和联系。 首先,依赖反转(Dependency...

    java中spring依赖注入的简单例子

    javaEE 开发中 现在最成熟的框架之一应该就是spring了 spring框架最强大的地方就是实现了依赖注入 也叫控制反转 最近的一个项目中用的就是 spring框架 spring框架是工厂模式的应用结合了MVC的设计思想 大家可以...

    Spring_01_入门篇_依赖注入(控制反转)_XML

    在`Spring_01_入门篇_依赖注入(控制反转)_XML`中,我们可以看到一个或多个XML配置文件,它们定义了bean的定义,包括bean的类名、属性以及它们之间的依赖关系。例如: ```xml ...

    Spring基础实例(依赖注入和控制反转)

    本实例主要探讨Spring框架的基础概念,包括依赖注入(Dependency Injection,简称DI)和控制反转(Inversion of Control,简称IoC)。这两个概念是Spring的核心特性,它们极大地简化了代码的维护和管理,提高了软件...

    理解Spring中的依赖注入和控制反转

    在Spring框架中,依赖注入(Dependency Injection,简称DI)和控制反转(Inversion of Control,简称IoC)是核心设计理念,它们旨在降低组件之间的耦合度,提高代码的可测试性和可维护性。理解这两个概念对于掌握...

    Spring IOC 控制反转

    它通过依赖注入(Dependency Injection, DI)和面向切面编程(Aspect Oriented Programming, AOP)等特性实现了对应用程序的解耦,并通过IOC(Inversion of Control)控制反转来简化配置和管理。 #### 二、Spring ...

    spring依赖注入的实现原理

    在Spring框架中,依赖注入是核心特性之一,通过控制反转(Inversion of Control,IoC)实现了组件之间的解耦。 ### 依赖注入的基本概念 依赖注入允许开发者在不修改代码的情况下,通过配置来改变对象间的依赖关系。...

Global site tag (gtag.js) - Google Analytics