深入探讨控制反转(Ioc)和依赖注入(DI)

Spring 中的控制反转(IoC

1IoC = Inversion of Control (由容器控制程序之间的关系)

IoC ,用白话来讲,就是由容器来控制程序中的各个类之间的关系,而非传统实现中,直接在代码中由程序代码直接操控。比如在一个类(A )中访问另外一个类中(B )的方法时,我们需要先去new 一个B 的对象,然后调用所需的方法。他们的关系很显然在程序代码中控制,同时它们之间的耦合度也比较大,不利于代码的重用。而我们现在把这种控制程序之间的关系交给Ioc 容器,让它去帮你实例化你所需要的对象,而你直接在程序中调用就可以了。这也就是所谓" 控制反转" 的概念的由来:控制权由应用程序的代码中转到了外部容器,控制权的转移,是所谓反转的由来

可能你不知道Ioc 容器到底,或确切的指的是什么?其实这里的容器就相当于一个工厂一样。你需要什么,直接来拿,直接用就可以了,而不需要去了解, 去关心你所用的东西是如何制成的,在程序中体现为实现的细节,这里就用到了工厂模式,其实Spring 容器就是工厂模式和单例模式所实现的。在第三章中我将会详细介绍SpringIoc 容器。

对于初学者,我想简单的先说明几点,不要把applicationContext.xml, 或带有bean 的配置文件理解为容器,它们只是描述了要用到的类(bean )之间的依赖关系。Spring 中的容器很抽象,不像Tomcat,Weblogic,WebSphere 等那样的应用服务器容器是可见的。SpringIoc 容器给人的感觉好像就是那些配置文件(applicationContext.xml, 我刚开始学时,也以为就是那些带bean 的配置文件,虽然它对你学习Spring 没什么影响,但如果想更深沉的了解就会迷茫的,我们在这里要正确理解SpringIoc 容器,以后对我们学习会有很大的帮助的。其实它的容器是有一些类和接口来充当的,你可能又会很迷茫。这就是它与别的框架的不同之处,这一点也正在体现了它的无侵入性的一点,不像EJB 需要专门的容器来运行,侵入性很大的重量级的框架。Spring 只是一种轻量级的无侵入性的框架。说白了SpringIoc 容器就是可以实例化BeanFactoryApplicationContext( 扩展了BeanFactory) 的类.

可能你对Ioc 容器还是不太理解,慢慢来,刚刚接触的人都会很迷茫。我现在通过讲解一个例子来说明它的工作原理,你可能会恍然大悟,原来如此简单。

首先打开你的IDE 编译器,我用的是Eclipse3.2+MyEclipse5.5.1

我举了一个大家比较熟悉的例子,用户登录验证。如果用户名为:admin 密码为:1234.

就会在控制台输出 恭喜你,登录成功! 反之输出“对不起,登录失败!”并在日志文件中记录登录信息。这里我用到的是log4j.( 日志记录器) 。我严格按分层思想和Spring 中提倡的按接口编程的思想来演练这个简单的例子。可能你会想这么简单的为什么要那么麻烦呢?怎么不用一个类就解决了,做为程序员,我们时刻要记住,我们的代码要易维护,可重用,易扩展,低耦合,高内聚。如果你了解这些原则,你自然会明白这样麻烦的好处了。

我先整体的讲解一下工程中的目录结果:如下图

 

首先我们看一下UserLogin 接口和它的实现部分UserLoginImpl

package com.chap2;

public interface UserLogin  {

   

    public boolean login(String userName,String passWord);

}

package com.chap2;

@SuppressWarnings ( "unused" )

public class UserLoginImpl implements UserLogin {

    public boolean login(String userName,String passWord) {

       if (userName.equals( "admin" ) && passWord.equals( "1234" ))

           return true ;

       else

           return false ;

    }

}

上面的接口只是简单的定义了一个方法,用于验证用户身份是否合法。在实现中只是简单的数据比较,实际当中应该从数据库中去取数据进行验证的,我们只要能说明问题就可以了。

然后在看一下业务逻辑层的UserService 这里应该再对该类进行提取接口的,我只是写了一个类,如果你有兴趣的话,你可以在加上接口像上面的UserLogin 一样,这样做是为了降低代码的耦合度,提高封装性。

package com.chap2.service;

import com.chap2.UserLogin;

public class UserService {

    private String userName ;

    private String passWord ;

    private UserLogin userLogin ;

    public void setUserName(String userName) {

       this . userName = userName;

    }

    public void setPassWord(String passWord) {

       this . passWord = passWord;

    }

    public void setUserLogin(UserLogin userLogin) {

       this . userLogin = userLogin;

    }

    public String validateUser() {

       if ( userLogin .login( userName , passWord ))

           return " 恭喜你,登录成功!" ;

       else

           return " 对不起,登录失败!" ;

      }

}

在这个业务方法里其实是对userLogin 中的方法的再次封装,这里面可能隐藏的用到了正面封装的模式(Facade )或叫做门面模式。它的好处是为了不让客户直接访问UserLogin 中的方法,在实际的开发中UserLogin 的方法应该放在DAO 层中,这里的DAO 是数据访问层的意思,其实就是对数据库执行的CRUD (增,删,改,查)操作。大家不要因为我多讲了一些就迷。我们应该养成好的编程习惯。也就是分工明细。层与层之间只能通过接口访问。至于上面提到的模式。你可以不先管了,只要你知道它里面用到了就可以了,以后我会把常用到的模式给你讲讲的。

这里的前台客户端的调用,我没用到html,jsp 之类的页面,只是用了一个main() 方法简单的模拟一下。同样可以起到上面讲的效果的。

public class Client {

    public static void main(String[] args) {

       // TODO 自动生成方法存根

       Log log = LogFactory.getLog (Client. class );

       // 初始化,并加载配置文件。

       ApplicationContext context = new ClassPathXmlApplicationContext(

              "applicationContext.xml" );

       BeanFactory beanFactory = (BeanFactory) context;

       // 得到实例化的UserService

       UserService userService = (UserService) beanFactory

              .getBean( "userService" );

       // 输出并记录登录信息

       log.info(userService.validateUser());

    }

}

先简单的看一下运行的结果是什么。

2007-11-21 11:04:44,765 INFO [org.springframework.context.support.ClassPathXmlApplicationContext] - <Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@c51355: display name [org.springframework.context.support.ClassPathXmlApplicationContext@c51355]; startup date [Wed Nov 21 11:04:44 CST 2007]; root of context hierarchy>

2007-11-21 11:04:44,843 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [applicationContext.xml]>

2007-11-21 11:04:45,031 INFO [org.springframework.context.support.ClassPathXmlApplicationContext] - <Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@c51355]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1ce2dd4>

2007-11-21 11:04:45,046 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - <Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1ce2dd4: defining beans [userLogin,userService]; root of factory hierarchy>

2007-11-21 11:04:45,078 INFO [com.chap2.util.Client] - < 恭喜你,登录成功!>

你可能会想登录用户的信息在那?

所有的配置信息和实例化的工作都交给了SpringIoc 容器,看看配置文件的配置。

<? 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-2.0.xsd" >

    < bean id = "userLogin" class = "com.chap2.UserLoginImpl" />

    < bean id = "userService" class = "com.chap2.service.UserService" >

       < property name = "userName" >

           < value > admin </ value >

       </ property >

       < property name = "passWord" >

           < value > 1234 </ value >

       </ property >

       < property name = "userLogin" >

           < ref bean = "userLogin" />

       </ property >

    </ bean >

</ beans >

上面的配置文件只是描述了我们所用到类的调用关系,和数据的赋值< 初始值> 。我们在UserSerivce 中要用到UserLogin 中的方法,我们只需在这里简单的设置它的一个属性(property) 就可以了,以前的编程要在代码中实现了,通过先new 一个UserLogin 的实现对象,然后在调用其中的方法。我们这里只需在配置文件中简单的配置一下就可以了,在程序用到这个方法时,容器会自动先实例化UserLoginImpl ,然后把它交给UserService 使用。在UserService 中不用担心实例化,以及管理它的生命周期了。全部让SpringIoc 容器管理记行了。这样做减少了它们之间的依赖性,也就是降低它们的耦合度。

整个程序的工作流程是这样的。

main() 方法中通过 ApplicationContext 来加载配置文件,然后把它转换为BeanFactory 。这就是我们要讲的Spring 中控制反转容器。它把所有的类都初始化,并放在这个容器中。在我们需要的时候只需像 UserService userService = (UserService) beanFactory.getBean( "userService" ); 通过配置文件中beanidname 调用就可以了。不必在像以前的编程那样UserService userSerivce=new UserService(); 这样做还有一个好处就是让容器来管理它的生命周期,我们只需用就可以了,用完了在交给容器管理。而不用担心什么时候销毁它。

附加一些log4j 的文件信息。就是工程目录下的log4j.properties. 一定要放到src 目录下,要不然程序运行时,它会找不到的。或直接把它放到class 目录下。Log4j 就是简单的记录一些日志信息,为以后使用的。下面是它的配置。其实还有多中形式的配置,可以是java 属性文件形式的。像userLogin.log, userLogin.log.1 就是一些备份文件。里面记录了程序运行时的一些信息。

log4j.rootLogger= INFO, stdout,logfile

 

log4j.appender.stdout= org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout= org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern= %d %p [%c] - <%m>%n

 

log4j.appender.logfile= org.apache.log4j.RollingFileAppender

log4j.appender.logfile.File= userLogin.log

log4j.appender.logfile.MaxFileSize= 2KB

# Keep three backup files.

log4j.appender.logfile.MaxBackupIndex= 3

# Pattern to output: date priority [category] - message

log4j.appender.logfile.layout= org.apache.log4j.PatternLayout

log4j.appender.logfile.layout.ConversionPattern= %d %p [%c] - %m%n

在这里我就不具体的讲了。在以后的章节中会讲到的。

2IOC 是一种使应用程序逻辑外在化的设计模式

因为提供服务的组件是被注入而不是被写入到客户机代码中。将 IOC 与接口编程应用结合从而产生出 Spring 框架的架构,这种架构能够减少客户机对特定实现逻辑的依赖。

3IoC 的设计目标

不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器(在 Spring 框架中是 IOC 容器) 负责将这些联系在一起。

4IoC 在应用开发中的体现

IoC 的抽象概念是“依赖关系的转移”,在实际应用中的下面的各个规则其实都是IoC 在应用开发中的体现。

l   “高层模块组件不应该依赖低层模块组件,而是模块组件都必须依赖于抽象”是 IoC 的一种表现

l   “实现必须依赖抽象,而不是抽象依赖实现”也是IoC 的一种表现

l   “应用程序不应依赖于容器,而是容器服务于应用程序”也是IoC 的一种表现。

接下来我们讲在讲述它的另外的一个名字:依赖注入(DI )。

Spring 中的 依赖注入(DI

1DI = Dependency Injection

正在业界为IoC 争吵不休时《Inversion of Control Containers and the Dependency Injection pattern 》为IoC 正名,至此,IoC 又获得了一个新的名字:“依赖注入(Dependency Injection )”。

Dependency Injection 模式是依赖注射的意思,也就是将依赖先剥离,然后在适当时候再注射进入。

2 )何谓依赖注入

相对IoC 而言,“依赖注入”的确更加准确地描述了这种古老而又时兴的设计理念。从名字上理解,所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。

讲的通俗点,就是在运行期,由Spring 根据配置文件,将其他对象的引用通过组件的 提供的setter 方法或者构造方法等进行设定。

在上面的UserService 中已经体现了这种方式,当UserService 需要的UserLogin 的 时候,容器会给它注入。这就体现了需要用的时候,有容器给你注入。你不必主动的如创建了。也不用如管理它了。是不是和以前的编程方式有些改变。可能你会想 到,这不就是工厂模式的衍生吗?不错它就是利用工厂模式的原理实现的,但它原比工厂模式简单。通过上面的部分代码我们就可以看出它就是工厂模式的衍生,BeanFactory 就充分说明了这一点。

 

我接下来通过讲一个比较接近生活中的例子来说名依赖注入的原理。

图解“依赖注入”(摘录网上资料)

1IT 人员的标准“行头”

上面是我们常用的工作装备,笔记本电脑一台、USB 硬盘和U 盘各一只。想必大家在日常工作中也有类似的一套行头。这与依赖注入有什么关系?

2 )图解“依赖注入”--- 在运行时由容器将依赖关系注入到组件中

图中三个设备都有一个共同点,都支持USB 接口。当我们需要将数据复制到外围存储设备时,可以

根据情况,选择是保存在U 盘还是USB 硬盘,下面的操作大家也都轻车熟路,无非接通USB 接口,然后在资源浏览器中将选定的文件拖放到指定的盘符。

这样的操作在过去几年中每天都在我们身边发生,而这也正是所谓依赖注入的一个典型案例,再看上例中,笔记本电脑与外围存储设备通过预先指定的一个接口(USB )相连,对于笔记本而言,只是将用户指定的数据发送到USB 接口,而这些数据何去何从,则由当前接入的USB 设备决定。

USB 设备加载之前,笔记本不可能预料用户将在USB 接口上接入何种设备,只有USB 设备接入之后,这种设备之间的依赖关系才开始形成。

对应上面关于依赖注入机制的描述,在运行时(系统开机,USB 设备加载)由容器(运行在笔记本中的Windows 操作系统)将依赖关系(笔记本依赖USB 设备进行数据存取)注入到组件中(Windows 文件访问组件)。这就是依赖注入模式在现实世界中的一个版本。

Spring 中为什么要提供“依赖注入”设计理念

1 )目的

依赖注入的目标并非为软件系统带来更多的功能,而是为了提升组件重用的概率,并为系统搭建一个灵活、可扩展的平台。

2 )原因--- 更简洁的编程实现

很多初学者常常陷入" 依赖注入,何用之有?" 的疑惑。想来前面和下面的例子可以帮助大家简单的理解其中的含义。

回顾上面创建Spring_chap2 的例子中,UserService 类在运行前,其userName,passWord 节点为空。运行后由容器将字符串"admin""1234" 注入。此时UserService 即与内存中的"admin""1234" 字符串对象建立了依赖关系。也许区区一个字符串我们无法感受出依赖关系的存在。

如果把这里的userName/passWord 属性换成一个数据源(DataSource ),可能更有感觉:

<beans>

<bean id="dataSource " class="org.springframework.indi.JndiObjectFactoryBean ">

                    <property name="jndiName">


连接池的配置交给容器

                     <value> java:/comp/env/ jdbc/testDB</value>

        </property>

</bean>

<bean id="dataBean" class="examples.DAOBean">

                    <property name="dataSource">

                     <ref bean="dataSource"/>

        </property>

</bean>

</beans>

其中 DAOBean (假设 DAOBean 是一个运行在J2EE 容器中的组件---Weblogic 或者Tomcat 等)中的dataSource 将由容器在运行期动态注入,而DataSource 的具体配置和初始化工作也将由容器在运行期完成。

对比传统的实现方式(如通过编码初始化DataSource 实例),我们可以看到,基于依赖注入的系统实现相当灵活简洁。

3 )产生的效果

通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定 DAOBean 中所需的DataSource 实例。 DAOBean 只需利用容器注入的DataSource 实例,完成自身的业务逻辑,而不用关心具体的资源来自何处、由谁实现。

l   提高了组件的可移植性和可重用度

假设我们的部署环境发生了变化,系统需要脱离应用服务器独立运行,这样,由于失去了容器的支持,原本通过JNDI 获取DataSource 的方式不再有效(因为,现在则需要改变为由某个组件直接提供DataSource )。

我们需要如何修改以适应新的系统环境?很简单,我们只需要修改dataSource 的配置:

<beans>

<bean id="dataSource" class=" org.apache.commons.dbcp.BasicDataSource " destroy-method="close">


连接池的配置交给 bean 好,容易迁移

         <property name="driverClassName">

            <value>com.microsoft.jdbc.sqlserver.SQLServerDriver</value>

        </property>

        <property name="url">

            <value>jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=WebStudyDB</value>

        </property>

        <property name="username">

            <value>sa</value>

        </property>

        <property name="password">

            <value>1234</value>

        </property>

</bean>

<bean id="dataBean" class="examples.DAOBean">

                    <property name="dataSource">

                     <ref bean="dataSource"/>

        </property>

</bean>

</beans>

这里我们的DataSource 改为由Apache DBCP 组件提供。没有编写任何代码我们即实现了DataSource 的切换。

l   依赖注入机制减轻了组件之间的依赖关系

回想传统编码模式中,如果要进行同样的修改,我们需要付出多大的努力。因此,依赖注入机制减轻了组件之间的依赖关系,同时也大大提高了组件的可移植性,这意味着,组件得到重用的机会将会更多。

 

接着我会在讲一个更现实的生活中的例子。

IoC 的另一种解释示例--- 生活中找“对象”

1 )控制倒(反)转

l   常规的方式--- 自己恋爱

举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm ,然后打听她们的兴趣爱好、qq 号、电话号、ip 号、iq 号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new 一个,或者从JNDI 中查询一个),使用完之后还要将对象销毁(比如Connection 等),对象始终会和其他的接口或类藕合起来。

l   借助于婚介(婚姻介绍所)找女朋友

那么IoC 是 如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它 我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm ,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。

2Spring 所倡导的开发方式--- 由容器帮助我们管理对象的生命周期和关系

l   我们只需要将对象在Spring 中进行登记

Spring 所倡导的开发方式就是如此,所有的类都会在Spring 容器中登记,告诉Spring 你是个什么东西,你需要什么东西,然后Spring 会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。

l   所有的类的创建、销毁都由Spring 来控制

所有的类的创建、销毁都由Spring 来控制,也就是说控制对象生存周期的不再是引用它的对象,而是Spring 。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被Spring 控制,所以这叫控制反转。

深入了解依赖注入

1IoC 的实现前提--- 借助于依赖注入

IoC 的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DIDependency Injection ,依赖注入)来实现的。

比如对象A 需要操作数据库,以前我们总是要在A 中自己编写代码来获得一个Connection 对象,有了Spring 我们就只需要告诉SpringA 中需要一个Connection ,至于这个Connection 怎么构造,何时构造,A 并不需要知道。

在系统运行时,Spring 会在适当的时候制造一个Connection ,然后像打针一样,注射到A 当中,这样就完成了对各个对象之间关系的控制。A 需要依赖Connection 才能正常运行,而这个Connection 是由Spring 注入到A 中的,依赖注入的名字就这么来的。

2 )如何实现依赖注入---- 通过reflection 来实现DI

那么DI 是如何实现的呢?Java 1.3 之后一个重要特征是反射(reflection ),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,Spring 就是通过反射来实现注入的。

利用下面的代码可以从配置文件中获得某个组件对象,并且动态地给该组件的message 属性赋值。

Properties pro = new Properties();

pro.load(new FileInputStream("config.properties"));

String actionImplName = (String)pro.get(actionBeanName);

String actionMessageProperty = (String)pro.get(actionMessagePropertyName);

Object obj = Class.forName(actionImplName).newInstance();

//BeanUtilsApache Commons BeanUtils 提供的辅助类

BeanUtils.setProperty(obj,"message", actionMessageProperty);

return (Action)obj;

 

Spring IOC 与工厂模式的对比

IOC(Inversion of Control) ,译作反转控制,其功能是将类之间的依赖转移到外部的配置文件中, 避免在调用类中硬编码实现类,因此也被称作依赖注入(Dependency Injection )。

在以往的开发中, 通常利用工厂模式(Factory )来解决此类问题---- 使外部调用类不需关心具体实现类,这样非常适合在同一个事物类型具有多种不同实现的情况下使用。其实不管是工厂模式还是依赖注入,调用类与实现类不可能没有任何依赖,工厂模式中工厂类通常根据参数来判断该实例化哪个实现类,Spring IOC 将需要实例的类在配置文件文件中配置。

使用Spring IOC 能得到工厂模式同样的效果,而且编码更加简洁。

1 )用工厂模式来实现的示例

    当我们在应用系统中的组件设计完全是基于接口定义时,一个关键问题便产生了----- 我们的程序如何去加载接口的各个实现类。在传统的解决方案种往往基于Factory 模式来实现。

l   Product.java (代表某种产品类的接口,也就是我们所要创建的对象所应该具有的功能要求)

public interface Product

{

public void execute();

}

l   不同的产品类(也就是我们所要创建的各个对象)

public class ConcreteProductA implements  Product   // ConcreteProductA.java

{

public void execute()

{

...

}

}

public class ConcreteProductB implements  Product   // ConcreteProductB.java

{

public void execute()

{

...

}

}

l   Factory.java (工厂类,利用它来创建出不同类型的产品--- 客户所需要的对象)

public class Factory

{

public Product CreateProduct(object param)

{

return ConstructObjects(param);

}

private Product ConstructObjects(object param)

{

...// 根据不同的产品类型的需求来创建不同的产品对象

}

}

l   Client.java( 调用类,也就是请求者类)

public class Client

{

public Client()

{

Product product = Factory.CreateProduct(paramA);  // 实例化ConcreteProductA

Product product = Factory.CreateProduct(paramB);  // 实例化ConcreteProductB

...

}

}

通过工厂模式,最终达到在ConstructObjects 方法中设定实例化实现类的逻辑,这样对于调用类来说,不直接实例化实现类(工厂模式中工厂类通常根据参数来判断该实例化哪个实现类),纵然实现类发生变化,而调用代码仍然可以不作修改,给维护与扩展带来便利---- 系统中的其他组件需要获取这个接口的实现,而无需事先获知其具体的实现。

    但采用工厂模式来实现时,将会有如下三个主要的缺点:

l   除非重新编译,否则无法对实现类进行替换。

必须重新编译工厂类使得原本可以达成的易用性大大降低。在过去,Spring 诞生之前,许多项目中,我们通过引入可配置化工厂类的形式,为这种基于接口的设计提供足够的支持。这解决了实例化的问题,但是它为我们的项目开发带来了额外的负担,同时,它也没有真正帮我们解决其余两个问题。

l   无法透明的为不同组件提供多个实现

这是我们在应用工厂模式时 一个比较头疼的问题,因为Factory 类要求每个组件都必须遵从Factory 类中定义的方法和结构特征。

当然我们可以在代码的实现的形式上为Factory 类中的ConstructObjects 方法增加一个参数, 通过该参数达到对接口实现的不同版本进行索引---- 这种实现方式的问题在于我们必须担负很大的维护工作量,每个组件都必须使用一个不同的关键字。从而使得它必须以一种与众不同的方式与其他组件的实例相区分。

l   无法简单的进行切换实例产生的模型---- 单例或者原形

上面的代码是实现了返回多个实例的方式,如果我们需要保持了一个Singleton 的实例,此时我们必须需要重新修改并编译Factory 类。

存在这个问题的核心是在于组件必须主动寻找接口的实现类,因此这个问题并不能通过传统的工厂模式加以解决。

2 )用Spring IOC 实现的示例 

l   SpringConfig.xml

<bean id="productA" class="ConcreteProductA" />

<bean id="productB" class="ConcreteProductB" />

 

l   InitSpring.java

public class InitSpring

{

AbstractApplicationContext wac = null;

private static InitSpring instance = new InitSpring();

private InitSpring()

{

}

public static void Init(AbstractApplicationContext wac)

{

instance.wac = wac;

}

public static Object getInstance(String objName)

{

return instance.wac.getBean(objName);

}

public static Object getInstance(Class objClass)

{

return getInstance(objClass.getName());

}

}

l   Client.java( 调用类)

public class Client

{

public Client()

{

Product product = (Product)InitSpring.getObject("productA");// 实例化ConcreteProductA

Product product = (Product)InitSpring.getObject("productB");// 实例化ConcreteProductB

...

}

}

对比调用代码,其中同样也没有硬编码实现类,但比较工厂模式,少了Factory 类而且采用配置文件来决定各个产品的实现类,使用Spring IOC 能得到工厂模式同样的效果,而且编码更加简洁、灵活方便。

Spring 对于基于接口设计的应用造成了极大的冲击效应。因为Spring 接过了将所有组件进行串联组装的重任,我们无需再纠缠于遍布各处的工厂类设计。

通过以上的这些通俗易懂的例子来解释Spring 中的核心思想。不知你理解了吗?如果还是不太明白,不用担心后面的章节讲解会让你彻底明白的。

 

原文地址:http://blog.csdn.net/weijie_search/archive/2008/03/10/2162411.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值