Skip to content

4. 依赖注入

依赖注入可以被视为控制反转的必然结果。我们将通过一个新示例来说明这一点。请看以下这个三层Web应用程序:

我们假设对DAO层的访问由前面讨论过的[IArticlesDao]接口控制,且整个应用程序是一个基于Web的购物应用程序。商品由[Dao]层管理。我们假设对业务层的访问由以下接口控制:

Public Interface IArticlesDomain
Public Interface IArticlesDomain
    ' buy a basket of items
    Sub acheter(ByVal panier As Panier)
    ' get the list of articles
    Function getAllArticles() As IList
    ' get a specific item
    Function getArticleById(ByVal idArticle As Integer) As Article
    ' to record errors
    ReadOnly Property erreurs() As ArrayList
End Interface

我们不会过多探讨各种方法的含义。我们只需指出,[getAllArticles] 方法(其作用是检索所有待售商品的列表)需要访问数据。为了获取数据,它必须调用 [IArticlesDao] 接口。实现 [IArticlesDomain] 接口的类的主体结构可能如下所示:

Imports istia.st.articles.dao
...
Namespace istia.st.articles.domain
    Public Class AchatsArticles
        Implements IArticlesDomain

        'private fields
        Private _articlesDao As IArticlesDao
        Private _erreurs As ArrayList

        ' manufacturer
        Public Sub New(ByVal articlesDao As IArticlesDao)
            _articlesDao = articlesDao
        End Sub
...
    End Class
End Namespace

如前所述,业务层接口的某些方法需要向 [Dao] 层请求数据。因此,我们实现 [IArticlesDomain] 接口的 [AchatsArticles] 类需要一个指向 [IArticlesDao] 接口实现类的引用。上文中的私有字段 [_articlesDao] 即充当此引用。该引用是在构造 [AchatsArticles] 对象时提供的。

假设 [ItemPurchases] 类已经编写完成,我们希望使用 [Nunit] 测试对其进行测试。让我们构建此测试,使其使用 Spring 配置文件:

...
Imports Spring.Objects.Factory.Xml
Imports System.IO
...
    <TestFixture()> _
    Public Class NunitSpringTestArticlesDomain

        ' the test object
        Private articlesDomain As IArticlesDomain

        <SetUp()> _
        Public Sub init()
      ' retrieve an instance of the Spring object manufacturer
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-domain.xml", FileMode.Open))
      ' request instantiation of the articles dao object
            articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDao)
        End Sub
...
    End Class

Spring 的配置文件 [spring-config-domain.xml] 内容会是什么?可能如下所示:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
    <description>Gestion d'une table d'articles</description>

     <!-- the IArticlesDao interface implementation class -->
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
      <constructor-arg index="0">
            <value>odbc-firebird-articles</value>
        </constructor-arg>
         <constructor-arg index="1">
            <value>SYSDBA</value>
        </constructor-arg>
         <constructor-arg index="2">
            <value>masterkey</value>
        </constructor-arg>
    </object>

     <!-- the IArticlesDomain interface implementation class -->
  <object id="articlesdomain" type="istia.st.articles.domain.AchatsArticles, articlesdomain">
      <constructor-arg index="0">
        <ref object="articlesdao" />
    </constructor-arg>
  </object>
</objects>

此文件此前已用于从 [Dao] 层实例化 [IArticlesDao] 单例,现在我们在此基础上添加了从业务层实例化 [IArticlesDomain] 单例的代码。这将如何构建?

  • 外部代码通过配置文件向 Spring 请求名为“articlesdomain”的单例引用。我们的测试类中的 [init] 方法就是如此:
        <SetUp()> _
        Public Sub init()
      ' retrieve an instance of the Spring object manufacturer
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-domain.xml", FileMode.Open))
      ' request instantiation of the articles dao object
            articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDao)
        End Sub
  • Spring 在其配置文件中查找该单例的定义。它发现要实例化该单例,需要另一个名为“articlesdao”的单例:
  <object name="articlesdomain" class="istia.st.articles.domain.AchatsArticles, articlesdomain">
      <constructor-arg index="0">
        <ref object="articlesdao" />
    </constructor-arg>
  </object>
  • 随后,Spring 将实例化“articlesdao”单例。我们之前已经解释过它是如何实现的。
  • 完成此操作后,它便可以实例化“articlesdomain”单例,并将对其的引用返回给请求它的代码。

该机制可通过以下图示进行说明:

我们可以看到,Spring 已管理了“articlesdomain”单例对“articlesdao”单例的依赖关系。要以这种方式使用 Spring,我们需要构建一个带有构造函数的类,该构造函数将依赖的单例作为参数:

Imports istia.st.articles.dao
...
Namespace istia.st.articles.domain
    Public Class AchatsArticles
        Implements IArticlesDomain

        'private fields
        Private _articlesDao As IArticlesDao

        ' manufacturer
        Public Sub New(ByVal articlesDao As IArticlesDao)
            _articlesDao = articlesDao
        End Sub
...
    End Class
End Namespace

“依赖注入”这一术语涵盖了以下两方面:

  • 基于依赖关系来构造待实例化类的具体方式
  • Spring 在实例化这些类时管理这些依赖关系的方式