Spring的DI和IoC实例详解

      这一章,我们开始讲解Spring的核心,那就是它的轻量级的控制反转(Inversion of Control,IoC)容器的功能。
5.1基本概念
        DI-Dependency Injection,依赖注入。
        Ioc-Inversion of Control,控制反转。

我 们首先来说说依赖(dependency)的概念。在java中,软件代码的逻辑组件和功能服务的划分,通常被定义为Java类或者对象的实例。而每一个 对象都必须使用其他的对象,或者与其他对象合作以便完成它的任务。比如,对于对象A,可以说凡是对象A涉及的其他对象都是它的依赖。

而依赖的产生和获得,就是依赖注入。

在旧有的Java中,习惯于自己new到一个实例,由类或者对象自身来管理自己的依赖实例。而IoC就是为了避免这一问题。这样子,我们就可以通过简单的配置,来改变获得的实例,而不用去修改大量的代码。我们在后面会细细讲来。下面我们就看看反转控制的概念和作用。

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

说白了,反转控制就是实现了依赖注入的类无关性,而是由容器来做。到这里,我们对这个基本的依赖注入和反转控制应该有了一个比较感性的认识了。下面我们来说明一下,Spring的IoC所实现的具体的依赖注入(DI)方式。
 
5.2IoC的依赖注入方式
        为了让大家快速入门,快速掌握Spring的精华,并且可以快速应用,所以我们就先不讲更多的原理性的东西(那些东西在没有一个比较感性的认识的情况下比较容易让人头晕了),我们直接通过例子讲IoC实现的具体的注入方式。

        IoC所定义的注入方式共有三种:
        1、设置器注入(Setterr Injection)。顾名思义,就是通过JavaBean的set方法。JavaBean的一些规范我们这里就不多讲了,如果不太了解的可以去这里看看 http://java.sun.com/products/javabeans ,最权威的文档和规范都在这里了。
        2、构造器注入(Constructor Injection)。就是通过类的构造器了,带参数的构造器来设置依赖的类实例或属性。
        3、方法注入(Method Injection)。这种方式使用的最少,就是通过普通的方法注入了。
        到这里三种注入形式我们都知道了,具体怎么来做,不用着急,后面我们会通过一个例子详细讲解。

        这里要感谢给我们提供了Spring框架的开发者们,我直接借用了他们提供的一个例子,简单的修改了一下。主要是时间有限,以后会给大家做一个自己的。例子可以直接在Eclipse下运行的,配置方法见前面的章节。
 


5.2.1传统方式注入
        我们下面来看具体的例子,注意包的路径。
       
WeatherService.java代码如下:

package ch02.sample1;

import java.util.Date;

/**
         * http://www.javastar.org
*/
public class WeatherService {
                  WeatherDao weatherDao = new StaticDataWeatherDaoImpl();
                  public Double getHistoricalHigh(Date date) {
                            WeatherData wd = weatherDao.find(date);
                            if (wd != null)
                              return new Double(wd.getHigh());
                            return null;
                  }
}

请注意这一句:WeatherDao weatherDao = new StaticDataWeatherDaoImpl();注意这里的依赖是通过接口和接口的实例直接new出来的,如果需要更换其它的接口实例,则需要修改这里的代码。


WeatherDao.java代码如下(注意这是一个Java接口):

package ch02.sample1;

import java.util.Date;

/**
* Data Access Object Interface for getting and storing weather records
* http://www.javastar.org
*/
public interface WeatherDao {

  /**
   * Returns the WeatherData for a date, or null if there is none
   * @param date the date to search on
   */
  WeatherData find(Date date);

  /**
   * Saves the WeatherData for a date
   */
  WeatherData save(Date date);

  WeatherData update(Date date);
}


StaticDataWeatherDaoImpl.java代码如下:

package ch02.sample1;

import java.util.Date;

/**
* Implementation of WeatherDao
* http://www.javastar.org
*/
public class StaticDataWeatherDaoImpl implements WeatherDao {

  public WeatherData find(Date date) {

    WeatherData wd = new WeatherData();
    wd.setDate((Date) date.clone());
    // some bogus values
    wd.setLow(date.getMonth() + 5);
    wd.setHigh(date.getMonth() + 15);
    return wd;
}

public WeatherData save(Date date) {
    throw new UnsupportedOperationException("This class uses static data only");
}

  /* (non-Javadoc)
   * @see ch02.sample1.WeatherDao#update(java.util.Date)
   */
  public WeatherData update(Date date) {
    throw new UnsupportedOperationException("This class uses static data only");
  }
}


WeatherData.java代码如下:

package ch02.sample1;

import java.util.Date;

/**
* Represents a daily weather record
* http://www.javastar.org
*/
public class WeatherData {

  Date date;

  double low;

  double high;

  /**
   * @return Returns the date.
   */
  public Date getDate() {
    return date;
  }

  public void setDate(Date date) {
    this.date = date;
  }

  /**
   * @return Returns the low.
   */
  public double getLow() {
    return low;
  }

  public void setLow(double low) {
    this.low = low;
  }

  /**
   * @return Returns the high.
   */
  public double getHigh() {
    return high;
  }

  public void setHigh(double high) {
    this.high = high;
  }
}


WeatherServiceTest.java代码如下:

package ch02.sample1;

import java.util.GregorianCalendar;

import junit.framework.TestCase;

/**
* http://www.javastar.org
*/
public class WeatherServiceTest extends TestCase {

  public void testSample1() throws Exception {
    WeatherService ws = new WeatherService();
    Double high = ws.getHistoricalHigh(new GregorianCalendar(2004, 0, 1).getTime());
    //  ... do more validation of returned value here, this test is not realistic
    System.out.println("High was: " + high);
  }
}

在Eclipse中直接运行WeatherServiceTest测试类,会得到如下的结果:
High was: 15.0
不过这个不重要,我们关注的是获得依赖对象的方式。
 






5.2.2设置器注入(Setter Injection)
现在我们来详细讲解设置器注入的方法,来改造上面的例子。

改造后的WeatherService.java源代码:

package ch02.sample2;

import java.util.Date;

/**
* http://www.javastar.org
*/
public interface WeatherService {
   Double getHistoricalHigh(Date date);
}

改造后的WeatherServiceImpl.java源代码:

package ch02.sample2;

import java.util.Date;

import ch02.sample2.WeatherDao;
import ch02.sample2.WeatherData;

/**
* http://www.javastar.org
*/
public class WeatherServiceImpl implements WeatherService {

  private WeatherDao weatherDao;
  
  public void setWeatherDao(WeatherDao weatherDao) {
    this.weatherDao = weatherDao;
  }


  public Double getHistoricalHigh(Date date) {
    WeatherData wd = weatherDao.find(date);
    if (wd != null)
      return new Double(wd.getHigh());
    return null;
  }
}

注意这几行代码,
  private WeatherDao weatherDao;
  
  public void setWeatherDao(WeatherDao weatherDao) {
    this.weatherDao = weatherDao;
  }
Spring容器就用setWeatherDao(WeatherDao weatherDao)来注入weatherDao属性的实例。


下面我们使用一个Spring应用程序上下文,即ClasspathXmlApplicationContext,来管理天气服务的实例,并且确保给出了一个天气DAO的实例来合作。首先,以XML的格式定义一个配置文件applicationContext.xml。


    "http://www.springframework.org/dtd/spring-beans.dtd">



  
   
      
   

  


  
  




简 单解释一下,beans元素中定义要有容器管理的每一个Bean实例的特征。Bean元素,定义要容器管理的一个Bean的相关属性;其中id是创建的 bean实例的名称,在其他位置通过该名称来得到该bean的实例;class指明创建该实例的类,允许存在同一个类的多个实例,以不同的名字来命名。 property指明要注入的属性,name就是该属性的名称。Ref来指定要注入的类实例,local表示在当前的xml中查找并校验所引用的实 例,local可以替换为bean或者parent,范围不同,以后我们会详细讲解。这里的其实就是指定将id为weatherDao的实例赋于weatherService的属性 weatherDao。


下面我们改造一下测试的类就可以了。
测试类WeatherServiceTest.java的源代码:
package ch02.sample2;

import java.util.GregorianCalendar;

import junit.framework.TestCase;

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

import ch02.sample2.WeatherService;

/**
* http://www.javastar.org
*/
public class WeatherServiceTest extends TestCase {

  public void testSample2() throws Exception {
    ApplicationContext ctx = new ClassPathXmlApplicationContext(
        "ch02/sample2/applicationContext.xml");
    WeatherService ws = (WeatherService) ctx.getBean("weatherService");

    Double high = ws.getHistoricalHigh(new GregorianCalendar(2004, 0, 1).getTime());
    //  ... do more validation of returned value here, this test is not realistic
    System.out.println("High was: " + high);
  }
}

这里就像上面说的,我们使用ClassPathXmlApplicationContext来管理天气预报类的实例。
其他的类和我们在5.2.1种的代码相同。运行结果如下:

2007-2-27 0:42:27 org.springframework.core.CollectionFactory
信息: JDK 1.4+ collections available
2007-2-27 0:42:27 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [ch02/sample2/applicationContext.xml]
2007-2-27 0:42:27 org.springframework.context.support.AbstractRefreshableApplicationContext refreshBeanFactory
信 息: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext;hashCode=15006066]: org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [weatherService,weatherDao]; root of BeanFactory hierarchy
2007-2-27 0:42:27 org.springframework.context.support.AbstractApplicationContext refresh
信 息: 2 beans defined in application context [org.springframework.context.support.ClassPathXmlApplicationContext;hashCode=15006066]
2007-2-27 0:42:27 org.springframework.context.support.AbstractApplicationContext initMessageSource
信 息: Unable to locate MessageSource with name 'messageSource': using default [org.springframework.context.support.DelegatingMessageSource@176c74b]
2007-2-27 0:42:27 org.springframework.context.support.AbstractApplicationContext initApplicationEventMulticaster
信 息: Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [org.springframework.context.event.SimpleApplicationEventMulticaster@95fd19]
2007-2-27 0:42:27 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信 息: Pre-instantiating singletons in factory [org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [weatherService,weatherDao]; root of BeanFactory hierarchy]
High was: 15.0

有兴趣的可以把StaticDataWeatherDaoImpl.java中的WeatherData wd = new WeatherData();改造一下,改造成设置器注入的方式。
 







5.2.3构造器注入

上面我们介绍了最常用的注入方法,设置器注入,下面我们开始讲解构造器注入的方法,对象的依赖为通过对象自己的构造函数来提供。
我们继续改造前面的例子。

WeatherService.java保持不变:

package ch02.sample3;

import java.util.Date;

/**
* http://www.javastar.org
*/
public interface WeatherService {
   Double getHistoricalHigh(Date date);
}

改造后的WeatherServiceImpl.java的源代码:
package ch02.sample3;

import java.util.Date;

import ch02.sample3.WeatherDao;
import ch02.sample3.WeatherData;

/**
*/
public class WeatherServiceImpl implements WeatherService {

  WeatherDao weatherDao;
  
  public WeatherServiceImpl(WeatherDao weatherDao) {
    this.weatherDao = weatherDao;
  }


  public Double getHistoricalHigh(Date date) {
    WeatherData wd = weatherDao.find(date);
    if (wd != null)
      return new Double(wd.getHigh());
    return null;
  }
}

        注意这里构造器加了参数WeatherDAO,而去掉了设置器。下面我们需要修改上下文配置文件。修改后的内容如下:

       
    "http://www.springframework.org/dtd/spring-beans.dtd">



  
   
      
   

  


  
  




注意这里的配置修改成了
   
      
   

而用设置器的时候是
       
      
   

配置文件的其它元素就不做重复的解释了。

        我们修改了这么多,而对于调用该服务的测试类毫无影响,所以不需要修改。运行测试类,同样得到如下的结果:

2007-3-1 18:02:00 org.springframework.core.CollectionFactory
信息: JDK 1.4+ collections available
2007-3-1 18:02:00 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [ch02/sample3/applicationContext.xml]
2007-3-1 18:02:00 org.springframework.context.support.AbstractRefreshableApplicationContext refreshBeanFactory
信 息: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext;hashCode=15006066]: org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [weatherService,weatherDao]; root of BeanFactory hierarchy
2007-3-1 18:02:00 org.springframework.context.support.AbstractApplicationContext refresh
信 息: 2 beans defined in application context [org.springframework.context.support.ClassPathXmlApplicationContext;hashCode=15006066]
2007-3-1 18:02:00 org.springframework.context.support.AbstractApplicationContext initMessageSource
信 息: Unable to locate MessageSource with name 'messageSource': using default [org.springframework.context.support.DelegatingMessageSource@116471f]
2007-3-1 18:02:00 org.springframework.context.support.AbstractApplicationContext initApplicationEventMulticaster
信 息: Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [org.springframework.context.event.SimpleApplicationEventMulticaster@11b9fb1]
2007-3-1 18:02:00 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信 息: Pre-instantiating singletons in factory [org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [weatherService,weatherDao]; root of BeanFactory hierarchy]
High was: 15.0
 






5.2.4方法注入

        现在我们讲解注入的最后一种形式,也是最少使用的一种形式:方法注入,在spring中也叫查询方法注入(Lookup Method Injection)。

        为了方便大家理解,先不讲理论了,我们先来看代码,这里我们需要改造的类文件还是WeatherServiceImpl.java,源代码如下:

        package ch02.sample4;

import java.util.Date;

import ch02.sample4.WeatherDao;
import ch02.sample4.WeatherData;

/**
*/
public abstract class WeatherServiceImpl implements WeatherService {

  protected abstract WeatherDao getWeatherDao();

  public Double getHistoricalHigh(Date date) {
    WeatherData wd = getWeatherDao().find(date);
    if (wd != null)
      return new Double(wd.getHigh());
    return null;
  }
}

注 意这一句protected abstract WeatherDao getWeatherDao();是个抽象方法,返回一个WeatherDao对象。还有这一句WeatherData wd = getWeatherDao().find(date);使用该抽象方法取得对象。有人会问,抽象方法没有实现怎么运行,后面我们会做解释,先不用着急。

配置文件当然也需要修改一下了,其他的文件就不需要了。修改后的配置文件内容如下:

       
    "http://www.springframework.org/dtd/spring-beans.dtd">



  
   
  


           class="ch02.sample4.StatefulDataWeatherDaoImpl">
  




注意了,这对weatherDao的声明没有太大变化,就是加了一句singleton="false",表示每次请求实例的时候返回一个新的对象实例,而不使用缓存。对于方法注入,我们主要看的是这里

和以前的property和constructor-age是不同的,而是用了lookup-method,所以我们又称方法注入为查询方法注入。
解 释一下其运行原理,我们在前面定义的抽象方法会由容器来实现方法,然后返回由容器查询得到的对象,这里就是 weatherDao,bean="weatherDao"就是指定了返回的对象。其主要用途就是处理单态、无状态对象需要使用非单态、有状态或者非线程 安全对象的时候。

这种注入方式很少使用,就不多讲了,大家有时间了可以深入研究一下。测试类修改如下:

package ch02.sample4;

import java.util.GregorianCalendar;

import junit.framework.TestCase;

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

import ch02.sample4.WeatherService;

/**
*/
public class WeatherServiceTest extends TestCase {

  public void testSample4() throws Exception {
    ApplicationContext ctx = new ClassPathXmlApplicationContext(
        "ch02/sample4/applicationContext.xml");
    WeatherService ws = (WeatherService) ctx.getBean("weatherService");

    Double high = ws.getHistoricalHigh(new GregorianCalendar(2004, 0, 1).getTime());
    //  ... do more validation of returned value here, this test is not realistic
    System.out.println("High was: " + high);
  }
  
  public void testToShowHowAbstractClassCanBeSubclassedJustForTest() {
   
    WeatherService ws = new WeatherServiceImpl() {
      protected WeatherDao getWeatherDao() {
        return null;
      }
    };
  }
}


测试结果如下:
2007-3-1 18:57:14 org.springframework.core.CollectionFactory
信息: JDK 1.4+ collections available
2007-3-1 18:57:14 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [ch02/sample4/applicationContext.xml]
2007-3-1 18:57:15 org.springframework.context.support.AbstractRefreshableApplicationContext refreshBeanFactory
信 息: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext;hashCode=15006066]: org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [weatherService,weatherDao]; root of BeanFactory hierarchy
2007-3-1 18:57:15 org.springframework.context.support.AbstractApplicationContext refresh
信 息: 2 beans defined in application context [org.springframework.context.support.ClassPathXmlApplicationContext;hashCode=15006066]
2007-3-1 18:57:15 org.springframework.context.support.AbstractApplicationContext initMessageSource
信 息: Unable to locate MessageSource with name 'messageSource': using default [org.springframework.context.support.DelegatingMessageSource@1a626f]
2007-3-1 18:57:15 org.springframework.context.support.AbstractApplicationContext initApplicationEventMulticaster
信 息: Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [org.springframework.context.event.SimpleApplicationEventMulticaster@1ee3914]
2007-3-1 18:57:15 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信 息: Pre-instantiating singletons in factory [org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [weatherService,weatherDao]; root of BeanFactory hierarchy]
High was: 15.0

注意这里需要用到cglib库,在Spring自带的lib中有cglib- nodep-2.1_3.jar。cglib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。很多框 架中都用到了该类库,比如hibernate。不多介绍了,有兴趣的自己去查一下相关的资料了。
 
最后说一下注入方法的选择,其实没有什么太多的原则,基本上用什么注入方法,在类设计的时 候就已经确定了。当然,提倡使用设置器注入,因为诸如太多的话,使用构造器会使代码比较冗长,比较难操作。强制不提供set方法的当然只能通过构造器,所 谓强制,举个例子,比如一个私有属性,不希望在运行的时候被修改,肯定不会提供set方法了,否则,就容易运行期间被无意间修改掉,所以只能通过构造器在 创建对象的时候设定。不多讲了,这个和类的目标设定有很大关系了。
关于测试程序的运行,我们只需要在eclipse中新建一个java project,然后引入相关的源代码即可。只是需要在工程的libraries中加入commons-logging.jar,junit- 4.2.jar,cglib-nodep-2.1_3.jar,当然还要有spring.jar。
然后选中test类文件选择运行为junit test的项目就行了。还有问题的话就发贴问吧,不要都通过QQ问了,人太多,回答不过来。
源代码见附件了。
 
本文讨论和源代码下载: http://www.javastar.org/thread-117-1-1.html
更多最新内容更新: http://www.javastar.org/thread-50-1-1.html

你可能感兴趣的:(Java)