Skip to content

4. 依赖注入

依赖注入可视为控制反转的自然结果。我们将通过一个新示例进行说明。考虑以下三层Web应用程序:

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

Public Interface IArticlesDomain
     ' 购买一篮商品
    Sub acheter(ByVal panier As Panier)
     ' 获取商品列表
    Function getAllArticles() As IList
     ' 获取特定商品
    Function getArticleById(ByVal idArticle As Integer) As Article
     ' 用于记录错误
    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 _articlesDao As IArticlesDao
        Private _erreurs As ArrayList

         ' 构造函数
        Public Sub New(ByVal articlesDao As IArticlesDao)
            _articlesDao = articlesDao
        End Sub
...
    End Class
End Namespace

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

假设已编写了类 [AchatsArticles],且希望使用类型为 [Nunit] 的测试用例对其进行测试。让我们构建该测试用例,使其使用 Spring 配置文件:

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

         ' 待测试对象
        Private articlesDomain As IArticlesDomain

        <SetUp()> _
        Public Sub init()
       ' 获取 Spring 对象生成器的实例
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-domain.xml", FileMode.Open))
       ' 请求实例化articles dao对象
            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>

     <!-- 接口的实现类 IArticlesDao -->
    <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>

     <!-- 接口的实现类 IArticlesDomain -->
  <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()
       ' 获取 Spring 对象生成器的实例
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-domain.xml", FileMode.Open))
       ' 请求实例化 articles dao 对象
            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 _articlesDao As IArticlesDao

         ' 构造函数
        Public Sub New(ByVal articlesDao As IArticlesDao)
            _articlesDao = articlesDao
        End Sub
...
    End Class
End Namespace

“依赖注入”这一术语同时涵盖:

  • 根据依赖关系构建待实例化类的特定方式
  • Spring在实例化这些类时管理这些依赖关系的方式