Skip to content

7. A solution

7.1. The Visual Studio project

Comments:

  • The files required by Spring have been placed in the [bin] folder: Spring.Core.dll, log4net.dll, Spring.Core.xml
  • The Spring configuration files [spring-config-3tier-*.xml] have also been placed in the [bin] folder
  • Under the [istia] root directory, you will find the various classes of the application

The project has been configured to generate the [spring3tier.dll] DLL in the [bin] folder:

Image

7.2. The package [istia.st.spring3tier.dao]

The IDao interface:

Namespace istia.st.spring3tier.dao
    Public Interface IDao
        ' do something in the [dao] layer
        Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

A first implementation class:

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

        ' do something in the [dao] layer
        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
  • The class has no private fields
  • The [doSomethingInDaoLayer] method returns the sum of its parameters as requested.

A second implementation class:

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

        ' do something in the [dao] layer
        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
  • The class has no private fields
  • The [doSomethingInDaoLayer] method returns the difference between its parameters as requested.

7.3. The [istia.st.spring3tier.domain] package

The IDomain interface:

Namespace istia.st.spring3tier.domain
    Public Interface IDomain
        ' do something in the [domain] layer
        Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

A first implementation class IDomainImpl1:

Imports istia.st.spring3tier.dao

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

        ' private fields
        Private _dao As IDao

        ' associated property
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

        ' default constructor
        Public Sub New()
        End Sub

        ' do something in the [domain] layer
        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
  • The class has a private field that is a reference to the singleton of type [IDao], which provides access to the [Dao] layer. This field will be initialized by Spring (dependency injection) when the object is constructed.
  • The [doSomethingInDomainLayer] method increments its parameters and then passes them to the [doSomethingInDaoLayer] method of the [dao] singleton

A second implementation class, IDomainImpl2:

Imports istia.st.spring3tier.dao

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

        ' private fields
        Private _dao As IDao

        ' associated property
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

        ' default constructor
        Public Sub New()
        End Sub

        ' do something in the [domain] layer
        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
  • The class has a private field that is a reference to the [IDao] singleton, which provides access to the [Dao] layer. This field will be initialized by Spring (dependency injection) when the object is constructed.
  • The [doSomethingInDomainLayer] method decrements its parameters and then passes them to the [doSomethingInDaoLayer] method of the [dao] singleton

7.4. The [istia.st.spring3tier.control] package

The IControl interface:

Namespace istia.st.spring3tier.control
    Public Interface IControl
        ' do something in the [control] layer
        Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

A first implementation class ControlImpl1:

Imports istia.st.spring3tier.domain

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

        ' private fields
        Private _domain As IDomain

        ' associated property
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

        ' default constructor
        Public Sub New()
        End Sub

        ' do something in the [control] layer
        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
  • The class has a private field that is a reference to the singleton of type [IDomain], which provides access to the [Domain] layer. This field will be initialized by Spring (dependency injection) when the object is constructed.
  • The [doSomethingInControlLayer] method increments its parameters and then passes them to the [doSomethingInDomainLayer] method of the [domain] singleton

A second implementation class, IControlImpl2:

Imports istia.st.spring3tier.domain

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

        ' private fields
        Private _domain As IDomain

        ' associated property
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

        ' default constructor
        Public Sub New()
        End Sub

        ' do something in the [control] layer
        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
  • The class has a private field that is a reference to the singleton of type [IDomain], which provides access to the [Domain] layer. This field will be initialized by Spring (dependency injection) when the object is constructed.
  • The [doSomethingInControlLayer] method decrements its parameters and then passes them to the [doSomethingInDomainLayer] method of the [domain] singleton.

7.5. The [Spring] configuration files

The [spring-config-3tier-1.xml] file uses version 1 of the implementations:

<?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 DAO class -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
    <!-- the domain class -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
    <!-- the control class -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

The [spring-config-3tier-2.xml] file uses version 2 of the implementations:

<?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 DAO class -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
    <!-- the domain class -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
    <!-- the control class -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

7.6. The test package [istia.st.spring3tier.tests]

A NUnit test [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
        ' singleton factories
        Private factory1 As XmlObjectFactory
        Private factory2 As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
            ' Create the singleton factories
            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()
            ' Destroy the singletons
            factory1.Dispose()
            factory2.Dispose()
            ' release the singleton factories
            factory1 = Nothing
            factory2 = Nothing
        End Sub

        <Test()> _
        Public Sub test()
            'Get an implementation of the IControl interface
            Dim control1 As IControl = CType(factory1.GetObject("control"), IControl)
            ' we use the class
            Dim a1 As Integer = 10, b1 As Integer = 20
            Dim res1 As Integer = control1.doSomethingInControlLayer(a1, b1)
            Assert.AreEqual(34, res1)
            ' we retrieve another implementation of the IControl interface
            Dim control2 As IControl = CType(factory2.GetObject("control"), IControl)
            ' we use the class
            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

Running this test yields the following results:

Image

Readers viewing a color version of this document will see that the results are "green," indicating that the test was successful.

7.7. Another type of Spring configuration file

A VB.NET application can be configured using a file called [App.config]. This file is placed at the root of the Visual Studio project:

Image

Spring can utilize this configuration file. Consider the following [App.config] file, inspired by examples from the [Spring.net] documentation:

<?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>
            <!-- an initial configuration -->
            <!-- the DAO class -->
            <object id="dao1" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
            <!-- the domain class -->
            <object id="domain1" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
                <property name="dao">
                    <ref object="dao1" />
                </property>
            </object>
            <!-- the control class -->
            <object id="control1" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
                <property name="domain">
                    <ref object="domain1" />
                </property>
            </object>
            <!-- a second configuration -->
            <!-- the DAO class -->
            <object id="dao2" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
            <!-- the domain class -->
            <object id="domain2" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
                <property name="dao">
                    <ref object="dao2" />
                </property>
            </object>
            <!-- the control class -->
            <object id="control2" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
                <property name="domain">
                    <ref object="domain2" />
                </property>
            </object>
        </objects>
    </spring>
</configuration>

Note: The information below is provided with reservations. I am not sure I have correctly understood the meaning of all the elements in the configuration file above.


The XML syntax of the [App.config] file requires that it follow this format:

<configuration>
....
</configuration>

Management of the various sections of [App.config] can be delegated to external programs. This is what is done here in the [ConfigSections] section:

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

The code above means that the section named [spring/context] must be managed by the class [Spring.Context.Support.ContextHandler] found in the [Spring.Core.dll] assembly, and that the section named [spring/objects] must be handled by the class [Spring.Context.Support.DefaultSectionHandler], which is also found in the [Spring.Core.dll] assembly.

The [spring/context] section is as follows:

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

This appears to indicate that the [spring/objects] section of the configuration file must be managed by the [Spring.Context.Support.XmlApplicationHandler] class, which can be found in the [Spring.Core.dll] assembly. This class must use an XML resource located at [config://spring/objects], i.e., the [spring/objects] section of the current configuration file.

In the [spring/objects] section, we find the Spring syntax we are now familiar with.

In the <objects>... </objects> section of the [App.config] file, we have defined two possible configurations for our 3-tier application:

  • one that uses version 1 of the interface implementations
  • another that uses version 2 of those same implementations

Now, how do you use the [App.config] file?

The following code shows a console application that uses the previous [App.config] file:

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()
            ' The Spring context that will allow us to retrieve the singletons
            Dim context As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
            ' We retrieve a first implementation of the IControl interface
            Dim control1 As IControl = CType(context.GetObject("control1"), IControl)
            ' We use the class
            Dim a1 As Integer = 10, b1 As Integer = 20
            Console.WriteLine("res1({0},{1})={2}", a1, b1, control1.doSomethingInControlLayer(a1, b1))
            ' retrieve another implementation of the IControl interface
            Dim control2 As IControl = CType(context.GetObject("control2"), IControl)
            ' we use the class
            Dim a2 As Integer = 10, b2 As Integer = 20
            Console.WriteLine("res2({0},{1})={2}", a2, b2, control2.doSomethingInControlLayer(a2, b2))
            ' pause
            Console.WriteLine("Press [Enter] to continue...")
            Console.ReadLine()
        End Sub

    End Module
End Namespace

Comments:

  • In the NUnit examples we’ve used so far, we used an [XmlObjectFactory] object to obtain the singletons we needed. Here, the object used is of type [IApplicationContext], a Spring interface. It is obtained from [App.config] using the [ConfigurationSettings] class, a class traditionally used in .NET to process configuration files.
  • We request the handler for the [spring/context] section. If we refer to the [App.config] file, we see that this handler is of type [XmlApplicationContext] and is responsible for managing the [spring/objects] section of [App.config].
  • Once the [IApplicationContext] object is retrieved, it is used just like the [XmlObjectFactory] object we have been using up to now.

The previous program is named [MainTestSpring3tier.vb] and is located in the [tests] package:

Image

The [spring3tier] project is configured so that [MainTestSpring3tier]:

Image

Running the project yields the following results:

res1(10,20)=34
res2(10,20)=-10
Press [Enter] to continue...

7.8. Conclusion

The Spring framework offers true flexibility in both application architecture and configuration. We used the IoC concept, one of Spring’s two pillars. The other pillar is AOP (Aspect-Oriented Programming), which we did not cover. It allows you to add “behavior” to a class method via configuration without modifying the method’s code. In simple terms, AOP allows you to filter calls to certain methods:

  • the filter can be executed before or after the target method M, or both.
  • The method M is unaware of these filters. They are defined in the Spring configuration file.
  • the code of method M is not modified. Filters are Java classes that must be implemented. Spring provides predefined filters, particularly for managing DBMS transactions.
  • Filters are beans and, as such, are defined in the Spring configuration file as beans.

A common filter is the transaction filter. Consider a business layer method M that performs two inseparable operations on data (a unit of work). It calls two DAO layer methods, M1 and M2, to perform these two operations.

Because it is in the business layer, method M abstracts away the underlying data storage. It does not, for example, need to assume that the data is in a DBMS and that it needs to place the two calls to methods M1 and M2 within a DBMS transaction. It is up to the DAO layer to handle these details. One solution to the previous problem is to create a method in the DAO layer that would itself call methods M1 and M2, wrapping these calls within a DBMS transaction.

The AOP filtering solution is more flexible. It allows you to define a filter that, before calling M, will start a transaction and, after the call, will perform a commit or rollback as appropriate.

There are several advantages to this approach:

  • once the filter is defined, it can be applied to multiple methods, for example, all those that require a transaction
  • the filtered methods do not need to be rewritten
  • since the filters to be used are defined by configuration, they can be changed

For more information: http://www.springframework.net.