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 є портуванням середовища JUnit, яке існує для платформи Java, на платформу .NET. У наведеному вище класі метод з атрибутом <SetUp()> виконується перед кожним тестовим методом. Метод з атрибутом <TearDown()> виконується після кожного тесту. У наведеному вище прикладі такого методу немає. Тут ми бачимо, що метод [init], який має атрибут <SetUp()>, створює екземпляр об’єкта [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 використовуватиме для створення екземпляра.

Об’єкти, які в додатку виконують роль надання послуги, часто створюються в єдиному екземплярі. Їх називають синглтонами. Так, у нашому прикладі багаторівневого додатка, представленому на початку цього документа, доступ до бази статей забезпечуватиме єдиний екземпляр класу [ArticlesDaoPlainODBC]. У веб-додатку такі об’єкти-сервіси обслуговують одразу декількох клієнтів. Не створюється окремий об’єкт-сервіс для кожного клієнта.

Наведений вище файл конфігурації Spring дозволяє створити єдиний об’єкт сервісу типу [ArticlesDaoPlainODBC] у пакеті з назвою [istia.st.articles.dao]. Три параметри, необхідні для конструктора цього об’єкта, задаються всередині тегів <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 це може бути метод [Application_Start] із файлу [global.asax]. Для нашого тестового класу [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))
       ' запитуємо створення екземпляра DAO-об'єкта «articles»
            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

Для проекту .NET, створеного у Visual Studio та призначеного для використання Spring, слід вчинити так:

  • три вищезазначені файли слід розмістити в папці [bin] проекту
  • [Spring.Core.dll] має бути додано до списку посилань проекту:

Image

  • класи, що використовують Spring, повинні імпортувати певні простори імен, серед яких часто зустрічається такий:
Imports Spring.Objects.Factory.Xml

Ще одне практичне питання: де розміщувати файл конфігурації Spring? Є кілька можливих місць. Одне з них — папка [bin] проекту. Саме туди було розміщено файл [spring-config-plainodbc.xml] з розглянутого прикладу.