Skip to content

2. 使用 Spring 配置应用程序

让我们考虑一个典型的三层架构应用程序:

假设对 DAO 层的访问由接口 [IArticlesDao] 控制:

....
Namespace istia.st.articles.dao

    Public Interface IArticlesDao
         ' 所有商品列表
        Function getAllArticles() As IList
         ' 添加商品
        Function ajouteArticle(ByVal unArticle As Article) As Integer
         ' 删除商品
        Function supprimeArticle(ByVal idArticle As Integer) As Integer
         ' 修改商品
        Function modifieArticle(ByVal unArticle As Article) As Integer
         ' 搜索文章
        Function getArticleById(ByVal idArticle As Integer) As Article
         ' 删除所有商品
        Sub clearAllArticles()
         ' 在交易中插入商品
        Sub doInsertionsInTransaction(ByVal articles As Article())
         ' 更改商品库存
        Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
    End Interface
End Namespace

在数据访问层(即 DAO 层,Data Access Object)中,经常需要处理 SGBD。假设通过 ODBC 驱动程序访问该对象。 访问该 ODBC 源的类的基本结构如下:

NameSpace istia.st.articles.dao

Imports System.Data.Odbc
...
    Public Class ArticlesDaoPlainODBC
        Implements istia.st.articles.dao.IArticlesDao

         ' 私有字段
        Private connexion As OdbcConnection = Nothing
        Private DSN As String

        Public Sub New(ByVal DSN As String, ByVal user As String, ByVal passwd As String)
             '获取源名称 ODBC
            Me.DSN = DSN
             ' 创建连接字符串
            Dim connectString As String = String.Format("DSN={0};UID={1};PWD={2}", DSN, user, passwd)
             '实例化连接
            connexion = New OdbcConnection(connectString)
        End Sub

....
    End Class
End NameSpace

要对源 ODBC 执行操作,任何方法都需要一个 [OdbcConnection] 对象,该对象代表与数据库的连接,数据库与应用程序之间的数据交换将通过此连接进行。要构建此对象,需要三项信息:

DSN As String
源名称 ODBC
user As String
创建连接时使用的身份
passwd As String
与该身份关联的密码

我们的类 [ArticlesDaoPlainODBC] 通过外部代理获取这些信息,该代理会实例化该类的某个成员。我们可能会好奇,该代理是如何获取实例化类 [ArticlesDaoPlainODBC] 所需的这三项信息的。 让我们举个例子。假设我们要编写一个用于测试 [Dao] 层的类。其架构如下:

Nunit 测试类 [http://www.nunit.org/] 的骨架可能如下所示:

Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports ArticlesDaoSqlmap = istia.st.articles.dao.ArticlesDaoSqlMap
Imports Article = istia.st.articles.domain.Article
Imports System.Threading

    <TestFixture()> _
    Public Class NunitTestArticlesDaoPlainOdbc

         ' 待测对象
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' 创建待测对象的实例
            articlesDao = New ArticlesDaoPlainODBC("odbc-firebird-articles", "SYSDBA", "masterkey")
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
             ' 目视检查
            listArticles()
        End Sub

         ' 屏幕截图
        Private Sub listArticles()
            Dim articles As IList = articlesDao.getAllArticles
            For i As Integer = 0 To articles.Count - 1
                Console.WriteLine(CType(articles(i), Article).ToString)
            Next
        End Sub


    End Class

Nunit 测试环境是将适用于 Java 平台的 JUnit 环境移植到 .NET 平台上的版本。 在上面的类中,带有 <SetUp()> 属性的方法会在每个测试方法之前执行。而带有 <TearDown()> 属性的方法则会在每个测试之后执行。在上面的示例中没有此类方法。 在此,我们可以看到带有 <SetUp()> 属性的 [init] 方法,通过硬编码的方式将该对象构造函数所需的三个参数直接传递给 [ArticlesDaoPlainODBC] 对象以进行实例化。

我们的测试类完全受制于其中某项硬编码信息的变更。更理想的做法是将这些信息写入配置文件,以避免在信息变更时进行不必要的重新编译。配置应用程序的常规解决方案是使用一个文件,其中包含所有可能随时间变化的信息。 配置文件种类繁多。当前的趋势是使用 XML 文件。Spring 框架就采用了这种方案。配置 [ArticlesDaoPlainODBC] 对象的文件可能如下所示:

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

<objects>
     <!-- IArticlesDao 接口的实现类 -->
    <description>Gestion d'une table d'articles</description>
    <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>
</objects>

Spring 配置文件描述了待实例化的对象。通常,它不会指定这些对象将在何时被实例化。因此,实例化的时机由使用该文件的代码决定。Spring 配置文件中描述的对象可以通过两种不同的方式进行实例化和初始化:

  • 如上所述,指定要传递给对象构造函数的参数
  • 为对象的属性(Property)提供值。在这种情况下,对象必须拥有一个默认构造函数,Spring 将使用该构造函数进行实例化。

在应用程序中,那些负责提供服务的对象通常只创建一个实例。这类对象被称为单例Singleton)。因此,在本文档开头介绍的多层应用程序示例中,对商品数据库的访问将由 [ArticlesDaoPlainODBC] 类的唯一实例来处理。 对于 Web 应用程序,这些服务对象会同时为多个客户端提供服务。我们不会为每个客户端创建一个服务对象。

上述 Spring 配置文件用于在名为 [istia.st.articles.dao] 的包中创建一个 [ArticlesDaoPlainODBC] 类型的唯一服务对象。 该对象构造器所需的三个参数定义在 <object>...</object> 标签内。需要构建的单例数量即为 <object> 标签的数量。

让我们详细说明该配置:

<objects>
...
</objects>

<objects> 是 Spring 配置文件的根标签。它声明了待实例化的单例对象的描述。

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

<description> 标签是可选的。例如,可以利用它来描述配置文件的作用。

<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
...    
</object>

<object> 标签用于描述待实例化的对象。它在此处具有两个属性:

  • name:对象的标识符。外部代码将通过此名称引用该对象。
  • class:格式为“类名, 程序集名”。第一部分是待实例化类的完整名称,第二部分是包含该类的 DLL 的名称。在本例中,该类位于名为 [articlesdao.dll] 的文件中

<object> 标签的内容用于描述对象的实例化方式:

    <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>

回顾[ArticlesDaoPlainODBC]类制造商的签名:

        Public Sub New(ByVal DSN As String, ByVal user As String, ByVal passwd As String)

Spring 对象 [articlesdao] 将通过上述构造函数实例化,并使用配置文件中的三项信息:[odbc-firebird-articles, SYSDBA, masterkey]。

Spring 配置文件中定义的对象何时会被构建?在任何应用程序中,都存在一个必定最先执行的方法。通常,单例的构建请求就在该方法中进行。 如果应用程序有 main 方法,则应用程序的初始化工作可交由该方法负责。对于应用程序 ASP.NET 而言,这可能是文件 [global.asax] 中的方法 [Application_Start]。 对于我们的测试类 [Nunit],应用程序的初始化在与 <Setup()> 属性关联的方法中进行。

如何在我们的 [Nunit] 类中使用上述配置文件?以下是一个示例:

Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports ArticlesDaoSqlmap = istia.st.articles.dao.ArticlesDaoSqlMap
Imports Article = istia.st.articles.domain.Article
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO

    <TestFixture()> _
    Public Class NunitSpringTestArticlesDaoPlainOdbc

         ' 待测对象
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
       ' 获取 Spring 对象生成器的实例
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-plainodbc.xml", FileMode.Open))
       ' 请求实例化 articles dao 对象
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
             ' 目视检查
            listArticles()
        End Sub

         ' 屏幕截图
        Private Sub listArticles()
            Dim articles As IList = articlesDao.getAllArticles
            For i As Integer = 0 To articles.Count - 1
                Console.WriteLine(CType(articles(i), Article).ToString)
            Next
        End Sub
    End Class

注释

  • 要实例化 Spring 配置文件中的对象,需通过一个类型为 [XmlObjectFactory] 的对象。这是一个“工厂”(Factory)类型的对象,即 c.a.d。该对象用于创建其他对象(Factory=工厂)。 根据所使用的配置文件,Spring 提供了多种“Factory”类型。此处使用的配置文件为 XML,因此我们使用 [XmlObjectFactory] 类型。
  • 从逻辑上讲,一个类型为 [XmlObjectFactory] 的对象需要配置文件 XML 的名称,此处即为 [spring-config-plainodbc.xml]。 确切地说,类型 [XmlObjectFactory] 的实例化需基于文件 XML 创建的读取流,并指定该文件的名称。
  • 一旦创建了类型为 [XmlObjectFactory] 的对象,即可通过 [XmlObjectFactory] 获取配置文件对象。getObject("标识符"),其中"标识符"是配置文件中某个对象的 [id] 属性。
  • 如果请求的对象尚未被实例化,Spring 将根据其配置文件中的信息对其进行实例化,并向调用程序返回该对象的引用。如果对象已被实例化,Spring 仅返回已存在对象的引用。这就是单例模式的原理。
  • 需要注意的是,测试类 [Nunit] 并不知道数据访问类的名称。该名称位于配置文件中。测试类只需请求一个实现 [IArticlesDao] 接口的对象即可:
         ' 待测对象
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
...
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

这正是 Spring 的魅力所在。如果更换了实现类,我们的测试类就无需修改。只需修改 Spring 的配置文件即可。测试类本身只需与接口交互,而非与具体类交互。

让我们以几个实用要点来结束本次介绍。

当我们写“Spring 将实例化……”时,具体是什么意思?对于 .NET 平台,Spring 包含在三个文件中:

Image

对于使用 Visual Studio 构建且需使用 Spring 的 .NET 项目,操作步骤如下:

  • 将上述三个文件放置在项目的 [bin] 文件夹中
  • [Spring.Core.dll] 必须作为项目引用:

Image

  • 调用 Spring 的类需要导入某些命名空间,通常包括以下命名空间:
Imports Spring.Objects.Factory.Xml

另一个实际问题:Spring配置文件应放置在哪里?有几个可能的位置。其中之一是项目的[bin]文件夹。本例中讨论的[spring-config-plainodbc.xml]文件就是放置在此处的。