Skip to content

7. Una soluzione

7.1. Il progetto Visual Studio

Commenti:

  • I file richiesti da Spring sono stati inseriti nella cartella [bin]: Spring.Core.dll, log4net.dll, Spring.Core.xml
  • Anche i file di configurazione di Spring [spring-config-3tier-*.xml] sono stati inseriti nella cartella [bin]
  • Nella directory principale [istia] sono presenti le varie classi dell'applicazione

Il progetto è stato configurato per generare la DLL [spring3tier.dll] nella cartella [bin]:

Image

7.2. Il pacchetto [istia.st.spring3tier.dao]

L'interfaccia IDao:

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

Una prima classe di implementazione:

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

        ' do something in the diaper [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
  • La classe non ha campi privati
  • Il metodo [doSomethingInDaoLayer] restituisce la somma dei suoi parametri come richiesto.

Una seconda classe di implementazione:

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

        ' do something in the diaper [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
  • La classe non ha campi privati
  • Il metodo [doSomethingInDaoLayer] restituisce la differenza tra i suoi parametri come richiesto.

7.3. Il pacchetto [istia.st.spring3tier.domain]

L'interfaccia IDomain:

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

Una prima classe di implementazione 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 builder
        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
  • La classe ha un campo privato che è un riferimento al singleton di tipo [IDao], il quale fornisce l'accesso al livello [Dao]. Questo campo verrà inizializzato da Spring (iniezione di dipendenze) al momento della creazione dell'oggetto.
  • Il metodo [doSomethingInDomainLayer] incrementa i propri parametri e li passa al metodo [doSomethingInDaoLayer] del singleton [dao]

Una seconda classe di implementazione, 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 builder
        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
  • La classe ha un campo privato che è un riferimento al singleton [IDao], il quale fornisce l'accesso al livello [Dao]. Questo campo verrà inizializzato da Spring (iniezione di dipendenze) al momento della creazione dell'oggetto.
  • Il metodo [doSomethingInDomainLayer] decrementa i propri parametri e li passa al metodo [doSomethingInDaoLayer] del singleton [dao]

7.4. Il pacchetto [istia.st.spring3tier.control]

L'interfaccia IControl:

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

Una prima classe di implementazione 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 builder
        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
  • La classe ha un campo privato che è un riferimento al singleton di tipo [IDomain], il quale fornisce l'accesso al livello [Domain]. Questo campo verrà inizializzato da Spring (iniezione di dipendenze) al momento della creazione dell'oggetto.
  • Il metodo [doSomethingInControlLayer] incrementa i propri parametri e li passa al metodo [doSomethingInDomainLayer] del singleton [domain]

Una seconda classe di implementazione, 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 builder
        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
  • La classe ha un campo privato che è un riferimento al singleton di tipo [IDomain], che fornisce l'accesso al livello [Domain]. Questo campo verrà inizializzato da Spring (iniezione di dipendenze) al momento della creazione dell'oggetto.
  • Il metodo [doSomethingInControlLayer] decrementa i propri parametri e li passa al metodo [doSomethingInDomainLayer] del singleton [domain].

7.5. I file di configurazione [Spring]

Il file [spring-config-3tier-1.xml] utilizza la versione 1 delle implementazioni:

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

Il file [spring-config-3tier-2.xml] utilizza la versione 2 delle implementazioni:

<?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. Il pacchetto di test [istia.st.spring3tier.tests]

Un test 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
        ' the singleton factories
        Private factory1 As XmlObjectFactory
        Private factory2 As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
            ' singleton factories are created
            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()
            ' we destroy the singletons
            factory1.Dispose()
            factory2.Dispose()
            ' we free the singletons factories
            factory1 = Nothing
            factory2 = Nothing
        End Sub

        <Test()> _
        Public Sub test()
            'we retrieve an implementation of the IControl interface
            Dim control1 As IControl = CType(factory1.GetObject("control"), IControl)
            ' we use the
            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
            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

L'esecuzione di questo test produce i seguenti risultati:

Image

I lettori che visualizzano una versione a colori di questo documento noteranno che i risultati sono "verdi", a indicare che il test ha avuto esito positivo.

7.7. Un altro tipo di file di configurazione Spring

Un'applicazione VB.NET può essere configurata utilizzando un file denominato [App.config]. Questo file si trova nella directory principale del progetto Visual Studio:

Image

Spring può utilizzare questo file di configurazione. Si consideri il seguente file [App.config], ispirato agli esempi della documentazione [Spring.net]:

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

Nota: le informazioni riportate di seguito sono fornite con riserva. Non sono sicuro di aver compreso correttamente il significato di tutti gli elementi presenti nel file di configurazione sopra riportato.


La sintassi XML del file [App.config] richiede che segua questo formato:

<configuration>
....
</configuration>

La gestione delle varie sezioni di [App.config] può essere delegata a programmi esterni. Questo è ciò che viene fatto qui nella sezione [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>

Il codice sopra riportato significa che la sezione denominata [spring/context] deve essere gestita dalla classe [Spring.Context.Support.ContextHandler] presente nell'assembly [Spring.Core.dll], e che la sezione denominata [spring/objects] deve essere gestita dalla classe [Spring.Context.Support.DefaultSectionHandler], anch'essa presente nell'assembly [Spring.Core.dll].

La sezione [spring/context] è la seguente:

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

Ciò sembra indicare che la sezione [spring/objects] del file di configurazione debba essere gestita dalla classe [Spring.Context.Support.XmlApplicationHandler], che si trova nell'assembly [Spring.Core.dll]. Questa classe deve utilizzare una risorsa XML situata in [config://spring/objects], ovvero la sezione [spring/objects] del file di configurazione corrente.

Nella sezione [spring/objects] troviamo la sintassi Spring che ormai conosciamo bene.

Nella sezione <objects>... </objects> del file [App.config], abbiamo definito due possibili configurazioni per la nostra applicazione a 3 livelli:

  • una che utilizza la versione 1 delle implementazioni dell'interfaccia
  • un'altra che utilizza la versione 2 di quelle stesse implementazioni

Ora, come si utilizza il file [App.config]?

Il codice seguente mostra un'applicazione console che utilizza il precedente file [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()
            ' the Spring context that will allow us to retrieve singletons
            Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
            ' we retrieve a 1st implementation of the IControl interface
            Dim control1 As IControl = CType(contexte.GetObject("control1"), IControl)
            ' we use the
            Dim a1 As Integer = 10, b1 As Integer = 20
            Console.WriteLine("res1({0},{1})={2}", a1, b1, control1.doSomethingInControlLayer(a1, b1))
            ' we retrieve another implementation of the IControl interface
            Dim control2 As IControl = CType(contexte.GetObject("control2"), IControl)
            ' we use the
            Dim a2 As Integer = 10, b2 As Integer = 20
            Console.WriteLine("res2({0},{1})={2}", a2, b2, control2.doSomethingInControlLayer(a2, b2))
            ' break
            Console.WriteLine("Tapez [entrée] pour continuer...")
            Console.ReadLine()
        End Sub

    End Module
End Namespace

Commenti:

  • Negli esempi NUnit che abbiamo utilizzato finora, abbiamo usato un oggetto [XmlObjectFactory] per ottenere i singleton di cui avevamo bisogno. Qui, l'oggetto utilizzato è di tipo [IApplicationContext], un'interfaccia Spring. Viene ottenuto da [App.config] utilizzando la classe [ConfigurationSettings], una classe tradizionalmente utilizzata in .NET per elaborare i file di configurazione.
  • Richiediamo l'handler per la sezione [spring/context]. Se consultiamo il file [App.config], vediamo che questo handler è di tipo [XmlApplicationContext] ed è responsabile della gestione della sezione [spring/objects] di [App.config].
  • Una volta recuperato l'oggetto [IApplicationContext], viene utilizzato proprio come l'oggetto [XmlObjectFactory] che abbiamo utilizzato fino ad ora.

Il programma precedente si chiama [MainTestSpring3tier.vb] e si trova nel pacchetto [tests]:

Image

Il progetto [spring3tier] è configurato in modo tale che [MainTestSpring3tier]:

Image

L'esecuzione del progetto produce i seguenti risultati:

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

7.8. Conclusione

Il framework Spring offre una vera flessibilità sia nell'architettura che nella configurazione delle applicazioni. Abbiamo utilizzato il concetto di IoC, uno dei due pilastri di Spring. L'altro pilastro è l'AOP (Aspect-Oriented Programming), che non abbiamo trattato. Esso consente di aggiungere un "comportamento" a un metodo di classe tramite configurazione senza modificare il codice del metodo. In termini semplici, l'AOP consente di filtrare le chiamate a determinati metodi:

  • il filtro può essere eseguito prima o dopo il metodo di destinazione M, oppure in entrambi i casi.
  • Il metodo M non è a conoscenza di questi filtri. Essi sono definiti nel file di configurazione di Spring.
  • Il codice del metodo M non viene modificato. I filtri sono classi Java che devono essere implementate. Spring fornisce filtri predefiniti, in particolare per la gestione delle transazioni DBMS.
  • I filtri sono bean e, in quanto tali, sono definiti nel file di configurazione di Spring come bean.

Un filtro comune è il filtro delle transazioni. Si consideri un metodo M del livello business che esegue due operazioni inseparabili sui dati (un'unità di lavoro). Esso chiama due metodi del livello DAO, M1 e M2, per eseguire queste due operazioni.

Poiché si trova nel livello business, il metodo M astrae l'archiviazione dei dati sottostante. Non deve, ad esempio, presumere che i dati si trovino in un DBMS e che sia necessario inserire le due chiamate ai metodi M1 e M2 all'interno di una transazione DBMS. Spetta al livello DAO gestire questi dettagli. Una soluzione al problema precedente consiste nel creare un metodo nel livello DAO che chiami a sua volta i metodi M1 e M2, racchiudendo queste chiamate all'interno di una transazione DBMS.

La soluzione di filtraggio AOP è più flessibile. Consente di definire un filtro che, prima di chiamare M, avvierà una transazione e, dopo la chiamata, eseguirà un commit o un rollback a seconda dei casi.

Questo approccio presenta diversi vantaggi:

  • una volta definito il filtro, può essere applicato a più metodi, ad esempio a tutti quelli che richiedono una transazione
  • non è necessario riscrivere i metodi filtrati
  • poiché i filtri da utilizzare sono definiti dalla configurazione, possono essere modificati

Per ulteriori informazioni: http://www.springframework.net.