2. 使用 Spring 配置应用程序
考虑一个典型的三层应用程序:
![]() |
我们将假设对 DAO 层的访问由一个接口 [IArticlesDao] 进行控制:
....
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' list of all items
Function getAllArticles() As IList
' add an article
Function ajouteArticle(ByVal unArticle As Article) As Integer
' deletes an article
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' modify an article
Function modifieArticle(ByVal unArticle As Article) As Integer
' search for an article
Function getArticleById(ByVal idArticle As Integer) As Article
' deletes all articles
Sub clearAllArticles()
' inserts items within a transaction
Sub doInsertionsInTransaction(ByVal articles As Article())
' changes the stock of an item
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
End Namespace
在数据访问层或 DAO(数据访问对象)层中,通常需要与数据库管理系统(DBMS)进行交互。假设通过 ODBC 驱动程序访问数据库。访问此 ODBC 源的类的基本结构如下:
NameSpace istia.st.articles.dao
Imports System.Data.Odbc
...
Public Class ArticlesDaoPlainODBC
Implements istia.st.articles.dao.IArticlesDao
' private fields
Private connexion As OdbcConnection = Nothing
Private DSN As String
Public Sub New(ByVal DSN As String, ByVal user As String, ByVal passwd As String)
'retrieve the source name ODBC
Me.DSN = DSN
' we create the connection string
Dim connectString As String = String.Format("DSN={0};UID={1};PWD={2}", DSN, user, passwd)
'we instantiate the connection
connexion = New OdbcConnection(connectString)
End Sub
....
End Class
End NameSpace
要在 ODBC 源上执行操作,每个方法都需要一个 [OdbcConnection] 对象,该对象代表与数据库的连接,数据将通过此连接在数据库和应用程序之间进行交换。要创建此对象,需要以下三项信息:
ODBC 数据源的名称 | |
用于登录的用户名 | |
与该身份关联的密码 |
我们的 [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
' the test object
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' create an instance of the object to be tested
articlesDao = New ArticlesDaoPlainODBC("odbc-firebird-articles", "SYSDBA", "masterkey")
End Sub
<Test()> _
Public Sub testGetAllArticles()
' visual check
listArticles()
End Sub
' screen listing
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 测试框架是 JUnit 框架(该框架原本面向 Java 平台)移植到 .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>
<!-- the IArticlesDao interface implementation class -->
<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 配置文件中描述的对象可以通过两种不同的方式进行实例化和初始化:
- 如上所示,通过指定要传递给对象构造器的参数
- 为对象的属性提供具体值。在这种情况下,对象必须拥有一个默认构造函数,Spring 将使用该构造函数进行实例化。
应用程序中负责提供服务的对象通常以单实例形式创建,这类对象被称为单例(singleton)。因此,在本文档开头介绍的多层应用程序示例中,对产品数据库的访问将由 [ArticlesDaoPlainODBC] 类的单个实例来处理。在 Web 应用程序中,这些服务对象会同时为多个客户端提供服务,我们不会为每个客户端分别创建服务对象。
上面的 Spring 配置文件允许您在名为 [istia.st.articles.dao] 的包中创建一个 [ArticlesDaoPlainODBC] 类型的服务对象。该对象构造函数所需的三个参数在 <object>...</object> 标签内定义。<object> 标签的数量将与要创建的单例数量相同。
让我们来分析一下该配置:
<objects> 是 Spring 配置文件的根标签。它声明了待实例化的单例对象的描述。
<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] 类的构造函数签名:
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
' the test object
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' retrieve an instance of the Spring object manufacturer
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-plainodbc.xml", FileMode.Open))
' request instantiation of the articles dao object
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
<Test()> _
Public Sub testGetAllArticles()
' visual check
listArticles()
End Sub
' screen listing
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 = 工厂)。Spring 根据所使用的配置文件提供了多种类型的“工厂”。这里,配置文件是一个 XML 文件,因此我们使用 [XmlObjectFactory] 类型。
- 从逻辑上讲,[XmlObjectFactory] 对象需要 XML 配置文件的名称,本例中为 [spring-config-plainodbc.xml]。更准确地说,[XmlObjectFactory] 类型是通过从指定名称的 XML 文件创建的读取流来实例化的。
- 创建 [XmlObjectFactory] 对象后,可通过 [XmlObjectFactory].getObject("identifier") 从配置文件中获取对象,其中 "identifier" 是配置文件中某个对象的 [id] 属性。
- 如果请求的对象尚未被实例化,Spring 会根据配置文件中的信息对其进行实例化,并将该对象的引用返回给调用程序。如果该对象已经实例化,Spring 则直接返回现有对象的引用。这就是单例原则。
- 请注意,[Nunit] 测试类并不知道数据访问类的名称。该名称位于配置文件中。测试类仅请求一个实现 [IArticlesDao] 接口的对象:
' the test object
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
...
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
这正是 Spring 的精髓所在。如果我们更改了实现类,测试类就无需修改。我们只需修改 Spring 配置文件即可。至于测试类,它只需与接口交互,而非具体类。
让我们用几个实用要点来结束本次讲解。
当我们写下“Spring 将实例化……”时,这究竟意味着什么?对于 .NET 平台,Spring 包含在三个文件中:

对于使用 Visual Studio 构建且需要使用 Spring 的 .NET 项目,请按以下步骤操作:
- 上述三个文件将放置在项目的 [bin] 文件夹中
- 必须将 [Spring.Core.dll] 添加到项目引用中:

- 且使用 Spring 的类必须导入特定命名空间,通常包括以下内容:
另一个实际问题:Spring 配置文件应放置在哪里?有几个可能的位置。其中之一是项目的 [bin] 文件夹。示例中的 [spring-config-plainodbc.xml] 文件就是放置在该位置的。

