Skip to content

7. 解决方案

7.1. Visual Studio 项目

注释

  • Spring 所需的文件已放置在 [bin] 文件夹中:Spring.Core.dll、log4net.dll、Spring.Core.xml
  • Spring的配置文件[spring-config-3tier-*.xml]也已放置在文件夹[bin]中
  • 在根目录 [istia] 下,包含应用程序的各个类

该项目已配置为在 [bin] 文件夹中生成 DLL 和 [spring3tier.dll]:

Image

7.2. [istia.st.spring3tier.dao] 包

IDao 接口:

Namespace istia.st.spring3tier.dao
    Public Interface IDao
         ' 在图层中执行某项操作 [dao]
        Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

一种初始实现:

Namespace istia.st.spring3tier.dao
    Public Class DaoImpl1
        Implements istia.st.spring3tier.dao.IDao

         ' 在图层中执行某项操作 [dao]
        Public Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDao.doSomethingInDaoLayer

            Return a + b
        End Function
    End Class

End Namespace
  • 该类没有私有字段
  • 方法 [doSomethingInDaoLayer] 按要求返回其参数的总和。

第二个实现类:

Namespace istia.st.spring3tier.dao
    Public Class DaoImpl2
        Implements istia.st.spring3tier.dao.IDao

         ' 在图层中执行某项操作 [dao]
        Public Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDao.doSomethingInDaoLayer

            Return a - b
        End Function
    End Class

End Namespace
  • 该类没有私有字段
  • 方法 [doSomethingInDaoLayer] 按要求返回其参数的差值。

7.3. 包 [istia.st.spring3tier.domain]

接口 IDomain

Namespace istia.st.spring3tier.domain
    Public Interface IDomain
         ' 在 [domain] 层中执行某项操作
        Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

IDomainImpl1 的首个实现类:

Imports istia.st.spring3tier.dao

Namespace istia.st.spring3tier.domain
    Public Class DomainImpl1
        Implements istia.st.spring3tier.domain.IDomain

         ' 私有字段
        Private _dao As IDao

         ' 关联属性
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

         ' 默认构造函数
        Public Sub New()
        End Sub

         ' 在 [domain] 层中执行某项操作
        Public Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDomain.doSomethingInDomainLayer
            a += 1
            b += 1
            Return _dao.doSomethingInDaoLayer(a, b)
        End Function
    End Class

End Namespace
  • 该类有一个私有字段,该字段引用了类型为 [IDao] 的单例,该单例提供对 [Dao] 层的访问。该字段将在对象构造时由 Spring(依赖注入)进行初始化。
  • 方法 [doSomethingInDomainLayer] 会递增其参数,然后将这些参数传递给单例 [dao] 的方法 [doSomethingInDaoLayer]

第二个实现类 IDomainImpl2

Imports istia.st.spring3tier.dao

Namespace istia.st.spring3tier.domain
    Public Class DomainImpl2
        Implements istia.st.spring3tier.domain.IDomain

         ' 私有字段
        Private _dao As IDao

         ' 关联属性
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

         ' 默认构造函数
        Public Sub New()
        End Sub

         ' 在 [domain] 层中执行某项操作
        Public Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDomain.doSomethingInDomainLayer
            a -= 1
            b -= 1
            Return _dao.doSomethingInDaoLayer(a, b)
        End Function
    End Class

End Namespace
  • 该类有一个私有字段,该字段引用了类型为 [IDao] 的单例,该单例提供对 [Dao] 层的访问。该字段将在对象构造时由 Spring(依赖注入)进行初始化。
  • 方法 [doSomethingInDomainLayer] 会递减其参数,然后将它们传递给单例 [dao] 的方法 [doSomethingInDaoLayer]

7.4. [istia.st.spring3tier.control] 包

接口 IControl

Namespace istia.st.spring3tier.control
    Public Interface IControl
         ' 在 [control] 层中执行某项操作
        Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

ControlImpl1 的首个实现类:

Imports istia.st.spring3tier.domain

Namespace istia.st.spring3tier.control
    Public Class ControlImpl1
        Implements istia.st.spring3tier.control.IControl

         ' 私有字段
        Private _domain As IDomain

         ' 关联属性
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

         ' 默认构造函数
        Public Sub New()
        End Sub

         ' 在 [control] 层中执行某项操作
        Public Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IControl.doSomethingInControlLayer
            a += 1
            b += 1
            Return _domain.doSomethingInDomainLayer(a, b)
        End Function
    End Class

End Namespace
  • 该类有一个私有字段,该字段引用了类型为 [IDomain] 的单例,该单例提供对 [Domain] 层的访问。该字段将在对象构造时由 Spring(依赖注入)进行初始化。
  • 方法 [doSomethingInControlLayer] 会递增其参数,然后将这些参数传递给单例 [domain] 中的方法 [doSomethingInDomainLayer]

第二个实现类 IControlImpl2

Imports istia.st.spring3tier.domain

Namespace istia.st.spring3tier.control
    Public Class ControlImpl2
        Implements istia.st.spring3tier.control.IControl

         ' 私有字段
        Private _domain As IDomain

         ' 关联属性
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

         ' 默认构造函数
        Public Sub New()
        End Sub

         ' 在 [control] 层中执行某项操作
        Public Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IControl.doSomethingInControlLayer
            a -= 1
            b -= 1
            Return _domain.doSomethingInDomainLayer(a, b)
        End Function
    End Class

End Namespace
  • 该类有一个私有字段,该字段引用了类型为 [IDomain] 的单例,该单例提供对 [Domain] 层的访问。该字段将在对象构造时由 Spring(依赖注入)进行初始化。
  • 方法 [doSomethingInControlLayer] 会递减其参数,然后将这些参数传递给单例 [domain] 的方法 [doSomethingInDomainLayer]。

7.5. 配置文件 [Spring]

文件 [spring-config-3tier-1.xml] 使用以下实现的第 1 版:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- DAO类 -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
     <!-- 域类 -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
     <!-- 控制类 -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

文件 [spring-config-3tier-2.xml] 使用以下实现的第 2 版:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- DAO类 -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
     <!-- 域类 -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
     <!-- 控制类 -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

7.6. 测试包 [istia.st.spring3tier.tests]

一个 Nunit 测试 [NunitTestSpring3tier.vb]:

Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.spring3tier.control
Imports istia.st.spring3tier.domain

Namespace istia.st.springioc.tests

    <TestFixture()> _
    Public Class NunitTestSpring3tier
         ' 单例工厂
        Private factory1 As XmlObjectFactory
        Private factory2 As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' 创建单例工厂
            factory1 = New XmlObjectFactory(New FileStream("spring-config-3tier-1.xml", FileMode.Open))
            factory2 = New XmlObjectFactory(New FileStream("spring-config-3tier-2.xml", FileMode.Open))
        End Sub

        <TearDown()> _
        Public Sub destroy()
             ' 销毁单例
            factory1.Dispose()
            factory2.Dispose()
             ' 释放单例工厂
            factory1 = Nothing
            factory2 = Nothing
        End Sub

        <Test()> _
        Public Sub test()
             '获取接口的实现IControl
            Dim control1 As IControl = CType(factory1.GetObject("control"), IControl)
             ' 使用该类
            Dim a1 As Integer = 10, b1 As Integer = 20
            Dim res1 As Integer = control1.doSomethingInControlLayer(a1, b1)
            Assert.AreEqual(34, res1)
             ' 获取接口的另一个实现 IControl
            Dim control2 As IControl = CType(factory2.GetObject("control"), IControl)
             ' 使用该类
            Dim a2 As Integer = 10, b2 As Integer = 20
            Dim res2 As Integer = control2.doSomethingInControlLayer(a2, b2)
            Assert.AreEqual(-10, res2)
        End Sub

    End Class
End Namespace

执行此测试得到以下结果:

Image

使用该文档彩色版本的读者会发现结果显示为“绿色”,这表明测试成功。

7.7. 另一种 Spring 配置文件类型

应用程序 VB.net 可通过名为 [App.config] 的文件进行配置。该文件位于 Visual Studio 项目的根目录下:

Image

Spring 可以利用此配置文件。以下是参考 [Spring.net] 文档中的示例编写的 [App.config] 文件:

<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
             <!-- 初始配置 -->
             <!-- DAO类 -->
            <object id="dao1" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
             <!-- 域类 -->
            <object id="domain1" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
                <property name="dao">
                    <ref object="dao1" />
                </property>
            </object>
             <!-- 控制类 -->
            <object id="control1" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
                <property name="domain">
                    <ref object="domain1" />
                </property>
            </object>
             <!-- 第二个配置 -->
             <!-- DAO类 -->
            <object id="dao2" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
             <!-- 域类 -->
            <object id="domain2" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
                <property name="dao">
                    <ref object="dao2" />
                </property>
            </object>
             <!-- 控制类 -->
            <object id="control2" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
                <property name="domain">
                    <ref object="domain2" />
                </property>
            </object>
        </objects>
    </spring>
</configuration>

:以下信息仅供参考。我不确定是否正确理解了上述配置文件中所有元素的含义。


文件 [App.config] 的语法 XML 要求其必须符合以下语法:

<configuration>
....
</configuration>

[App.config]中各部分的处理可委托给外部程序。[ConfigSections]部分即采用了这种做法:

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

上述代码表示名为 [spring/context] 的部分应由 [Spring.Context.Support.ContextHandler] 类管理,该类该类位于[Spring.Core.dll]组件中;而名为[spring/objects]的段应由[Spring.Context.Support.DefaultSectionHandler]类管理,该类同样位于[Spring.Core.dll]组件中。

[spring/context] 部分如下:

    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
...
    </spring>

这似乎表明,配置文件中的 [spring/objects] 部分应由 [Spring.Core.dll] 组件中的 [Spring.Context.Support.XmlApplicationHandler] 类管理。 该类必须使用资源 XML,其位置为 [ config://spring/objects]、c.a.d 以及当前配置文件中的 [spring/objects] 部分。

在 [spring/objects] 部分中,我们可以看到我们已经习惯的 Spring 语法。

在文件 [App.config] 的 <objects>... </objects> 部分中,我们为三层应用定义了两种可能的配置:

  • 一种使用接口实现的第 1 版
  • 另一种使用这些接口的第 2 版实现

那么,如何使用文件 [App.config] 呢?

以下代码展示了一个利用前述 [App.config] 文件的控制台应用程序:

Imports System
Imports Spring.Context
Imports System.IO
Imports NUnit.Framework
Imports istia.st.spring3tier.control
Imports istia.st.spring3tier.domain
Imports System.Configuration

Namespace istia.st.springioc.tests

    Module MainTestSpring3tier

        Public Sub main()
             ' Spring 上下文,用于获取单例
            Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
             ' 获取接口的第一个实现IControl
            Dim control1 As IControl = CType(contexte.GetObject("control1"), IControl)
             ' 使用该类
            Dim a1 As Integer = 10, b1 As Integer = 20
            Console.WriteLine("res1({0},{1})={2}", a1, b1, control1.doSomethingInControlLayer(a1, b1))
             ' 获取该接口的另一个实现 IControl
            Dim control2 As IControl = CType(contexte.GetObject("control2"), IControl)
             ' 使用该类
            Dim a2 As Integer = 10, b2 As Integer = 20
            Console.WriteLine("res2({0},{1})={2}", a2, b2, control2.doSomethingInControlLayer(a2, b2))
             ' 暂停
            Console.WriteLine("Tapez [entrée] pour continuer...")
            Console.ReadLine()
        End Sub

    End Module
End Namespace

注释

  • 在我们迄今为止使用的 NUnit 示例中,我们使用 [XmlObjectFactory] 对象来获取所需的单例。此处使用的对象类型为 [IApplicationContext],这是一个 Spring 接口。 它是通过 [ConfigurationSettings] 类从 [App.config] 派生而来的,该类通常用于 .NET 中以处理配置文件。
  • 我们请求 [spring/context] 节点的管理器。 若查阅文件 [App.config],会发现该文件属于 [XmlApplicationContext] 类型,且负责管理 [App.config] 中的 [spring/objects] 部分。
  • 一旦获取了类型为 [IApplicationContext] 的对象,其使用方式与我们此前使用的 [XmlObjectFactory] 对象相同。

上述程序名为 [MainTestSpring3tier.vb],位于 [tests] 包中:

Image

项目 [spring3tier] 已配置为使用 [MainTestSpring3tier]:

Image

执行该项目后,结果如下:

res1(10,20)=34
res2(10,20)=-10
Tapez [entrée] pour continuer...

7.8. Conclusion

Spring框架在应用程序架构和配置方面都提供了真正的灵活性。我们采用了IoC这一概念,它是Spring的两大支柱之一。 另一个支柱是 AOP(面向切面编程),我们尚未介绍。它允许通过配置为类的方法添加“行为”,而无需修改该方法的代码。简而言之,AOP 可以过滤对某些方法的调用:

  • 过滤器可在目标方法 M 之前、之后或两者同时执行。
  • 方法 M 不会感知这些过滤器的存在。这些过滤器在 Spring 的配置文件中定义。
  • 方法 M 的代码不会被修改。过滤器是需要构建的 Java 类。Spring 提供了预定义的过滤器,特别是用于管理 SGBD 的事务。
  • 过滤器是 Bean,因此作为 Bean 在 Spring 配置文件中进行定义。

一种常见的过滤器是事务过滤器。 假设业务层有一个方法 M,该方法对数据执行两项不可分割的操作(工作单元)。它调用 DAO 层中的两个方法 M1 和 M2 来完成这两项操作。

由于位于业务层,方法 M 忽略了这些数据的存储介质。例如,它无需 需要假设数据位于 SGBD 中,并将其对 M1 和 M2 方法的两次调用置于 SGBD 的事务中。 这些细节应由 DAO 层负责处理。 因此,解决上述问题的方案是在 DAO 层创建一个方法,该方法将自行调用 M1 和 M2 方法, 并将这些调用封装在 SGBD 的事务中。

AOP 的过滤方案更为灵活。它允许定义一个过滤器,该过滤器将在调用 M 之前启动一个事务,并在调用结束后根据具体情况执行提交或回滚。

这种方法有以下几个优点:

  • 一旦定义了过滤器,即可将其应用于多个方法,例如所有需要事务的方法
  • 经过筛选的方法无需重写
  • 由于要使用的过滤器通过配置定义,因此可以进行更改

更多信息请参阅:http://www.springframework.net。