Необходимые возможности

Рассматривая код StudentDAO в предыдущих частях вы не могли не обратить внимание на ситуацию, что «заходя» внутрь методов мы каждый раз создавали объект типа Session, открывали транзакцию, производили какие-то действия с базой данных и закрывали транзакцию. Это приходится делать случае использования метода getCurrentSession. Чуть ли не единственным положительным моментом здесь было то, что сессия закрывалась автоматически. Все остальные моменты выглядят не столь радужно. И постоянный вызов getCurrentSession, beginTransaction, commit. И невозможность выйти за рамки метода. Ведь в случае, если хотелось сделать несколько вызывов методовStudentDAO в рамках одной транзакции мы это сделать просто не могли.
Вызов метода openSession вместо getCurrentSession помогал решить эту проблему, но тогда приходилось брать на себя управление моментом создания и закрытия объекта Session.
есть еще один момент: как упоминалось ранее, корректным использованием шаблона DAO является вариант, когда Business layer обращается к интерфейсу, а не к классу. Это очень популярный (и во многих случаях оправданный) прием проектирования — определить интерфейс с тем, чтобы дать возможность подключать разные реализации. Примером может быть та же самая система Swing, которая позволяет в частности для объекта JTableреализовать разные варианты интерфейса TableModel. Такой прием дает мощные рычаги при реализации и поэтому использутеся достаточно часто.
Также существенным удобством может быть использование настраиваемой конфигурации при самой разработке — для тестирования она одна, для реальной работы — другая. И переключение между ними делается сменой одного флага.
Здесь существует один не совсем удобный момент: где-то в коде придется писать строки для создания конкретной реализации. Для уменьшения количества мест, где это надо делать часто используется шаблон проектирования AbstractFactory. В этом классе обычно есть метод, который возвращает нужную нам имплементацию интерфейса. Но что делать, если у вас нет возможности изменить Factory ? В общем-то можно решить и эту проблему — например наследовать. Но это тоже не всегда может сработать. Да и к тому же зачем изобретать велосипед ?
Сегодня существуют специальный термин — Inversion of Control (IoC). Он подразумевает наличие специального инструмента (пакета), который позволяет в определенной форме указать классы, объекты и отношения между ними. В процессе инициализации специальная подсистема загружаетуказанную конфигурацию, создает указанные объекты, а также создает связи между ними. В итоге вы получаете уже готовый набор объектов со всеми связями. Вам остается только попросить подсистему дать нужный объект.

После некоторого отступления (но оно нам тоже понадобится) вернемся к обсуждению проблем, связанных с работой с Session. Как видим, было бы хорошо иметь механизм, который позволял бы указывать некоторое стандартное/постоянное/однообразное действие при вызове каких-либо методов какого-либо класса. (Это не плохой слог, я думал именно так — каких-либо методов какого-либо класса). Иными словами — было бы здорово, если бы существовал способ, который автоматически начинал транзакцию и заканчивал ее (в начале метода и в конце — соответственно). Нам надо только указать в каких методах это надо сделать — и пусть все происходит автоматически.
И такой механизм тоже существует и носит имя Aspect Oriented Programming — AOP (Аспектно Ориентированное Программирование).
Необходимое действие, которое должно вызываться в определенные моменты и носит название аспект. Аспект требуется не только в случае сSession (или транзакциями).
Например его удобно использовать при отладке. Каждый важный метод можно предварять записью в логе. Причем с помощью Reflection вы можете в стандартном виде печатать все параметры.
Еще один вариант применения аспектов — система допуска к методам. С помощью аннотаций или записей в базе данных вы можете указать имена ползователей (или роди), которые могут выполнить данный метод.

Как мы видим, наличие системы, которая могла бы взять на себе такой круг задач, достаточно востребовано. И как мы знаем, спрос рождает предложение.
Одной из первых попыток создать такую систему являлась технология Enterprise Java Beans. Первый блин получился как всегда. Система была удобна при конфигурировании, требовала наличие EJB-сервера. Стоит отметить, что сегодня EJB представляет из себя достаточно удобную технологию и единственным «недостатком» можно считать необходимость использования EJB-сервера. Есть конечно еще кое-какие неудобства, но я бы не назвал их недостатками. Скорее это особенности.

Но на момент создания Spring недостатки EJB были слищком велики и Spring победно зашагал по планете Java.
Spring не требует EJB-сервера, достаточно прост в вопросах настройки и обладает широкими возможностями в тех областях, о которых мы говорили выше. Сегодня проект Spring включает в себя достаточно большое количество расширений и пакетов для реализации различных задач. Что касается непосредственно ядра — я бы выделил 4 основные функциональности Spring:

  1. Inversion of Control
  2. Aspect Oriented Programming
  3. Работа с базами данных
  4. Шаблон MVC (Model View Controller) для Web

Как и для пакета Hibernate мы посмотрим простой пример для одной сущности — Profession. Но на этом примере мы рассмотрим первые три части.

 

Библиотека

Для текущего проекта нам понадобятся следующие библиотеки:
antlr-2.7.6.jar
asm.jar
cglib-2.1.jar
commons-collections-3.1.jar
commons-logging.jar
dom4j-1.6.1.jar
ejb3-persistence.jar
hibernate-annotations.jar
hibernate-commons-annotations.jar
hibernate3.jar
javaee.jar
javassist-3.4.GA.jar
jta-1.1.jar
log4j-1.2.15.jar
mysqlJDBC-3.1.13.jar
slf4j-api-1.5.3.jar
slf4j-log4j12-1.5.3.jar
spring.jar

Описание функциональности

Начнем с того, что опишем необходимые функции, которые нам потребуются для работы с Profession. После этого укажем классы и интерфейсы, которые нам помогут реализовать необходимый набор функций.

Данный список не претендует на абсолютную наполненность, но для примера нам и этого хватит. Итак:

  1. Добавить Profession
  2. Редактировать Profession
  3. Удалить Profession
  4. Получить список Profession

 

С учетом того, что нам нужен класс для реализации бизнес-логики, интерфейс для DAO и хотя бы одна реализация DAo получаем 2 класса и один интерфейс:

  • Класс ProfessionFacade
  • Интерфейс ProfessionDAO
  • Класс ProfessionDAOImpl — имплементация для интерфейса ProfessionDAO
  • Класс ProfessionDAOImpl2 — еще одна имплементация для интерфейса ProfessionDAO
  • Класс Profession — это наш Entity, но пока мы его сделаем просто классом. Свяжем с базой позже
  • Класс ProfessionView — класс для бизнес-уровня. Мы это обсуждали ранее. Это просто класс для хранения данных, которые будут использованы для интерфейса пользователя (Web или GUI)

Таким образом методы (даже не методы, и набор заглушек) наших классов будут выглядеть следующим образом:

 

ProfessionFacade.java

ProfessionDAO.java

ProfessionDAOImpl.java

ProfessionDAOImpl2.java

Profession.java

ProfessionView.java

Единственный момент, на который я прошу обратить ваше внимание — поле в классе ProfessionFacade — dao. Оно имеет тип, который у нас является интерфейсом — ProfessionDAO. А реализации я сделал аж две штуки — ProfessionDAOImpl и ProfessionDAOImpl2. Чуть позже мы увидим насколько просто нам заменить одну реализацию на другую — исходный код не изменится. Его даже перекомпилировать не придется.

Inversion of Control

Пока мы не будем создавать код для работы с базой данных и применения аспектов. Усложним в соответствующее время. Здесь же рассмотрим толькоIoC-часть. И даже здесь мы рассмотрим очень-очень-очень простой пример. Если вас заинтересуют возможности Spring для инициализации — обращайтесь к документации к пакету — если вы читаете на английском, то лучше документации я не знаю. Можно конечно говорить, что где-то примеры интереснее или объяснение понятнее, но вряд ли где-то вы найдете более полное описание. Так что садитесь … и читайте. Но в другой раз.

А пока мы посмотрим каким образом можно загрузить сразу набор объектов для нашей несложной задачи. Если рассматривать грубо, то Spring внутрь своего контекста загружает все необходимое и вы можете получить это по имени. Получается что-то вроде контейнера, внутри которого «живут» объекты, указанные в конфигурации.

Наиболее важным в данном случае являются два момента:
— Файл конфигурации
— Код для загрузки конфигурации и использование результатов этой загрузки
Давайте сперва посмотрим на файл конфигурации — StudentExample.xml. Я его поместил прямо в каталог scr моего проекта в NetBeans. Так что будьте внимательны.

Здесь все достаточно несложно — я описываю объект (bean) с определенным именем, по которому я смогу получить его из контекста Spring.
У бина с именем professionFacade есть свойство dao. Это свойство, которое можно установить с помощью метода setDao). Но значение этого свойства берется как ссылка на другой объект — professionDAO_1. Бин с именем professionDAO_1 имеет тип ProfessionDAOImpl. Этот тип реализует интерфейс ProfessionDAO и следовательно прекрасно может быть подключен к полю dao у объекта класса ProfessionFacade. Для того, чтобы подключить объект другого класса — а именно professionDAO_2 — нам надо будет просто поменять значение у свойства dao и перезапустить наше приложение. Никаких компиляций, никаких правок кода. Ничего такого делать не приходится — даже и не думайте об этом.
Еще раз повторюсь — мы здесь рассматриваем крайне простую конфигруацию. Все может быть гораздо сложнее — вы можете указать, сколько объектов может быть создано для одного бина. Да-да — совсем не значит, что бинов с именем professionFacade будет один. Вы можете указать какой бин должен быть загружен раньше, чем другой — если конфигурация очень сложная. Вы можете указать простые значение для свойств — например для строк, чисел и т.д. Также вы можете указать конструктор для создания бинов — у нас он простой, без параметров. Также вы можете указать время создания бинов — сразу создавать при загрузке или только когда потребуется. Но все это я советую вам почитать в документации.
Моя задача сейчас — показать, что вы можете создать конфигурацию в виде описания и по этому описанию Spring вам создаст все необходимые объекты со всеми отношениями. Так что давайте посмотрим, как Spring может загрузить нашу конфигурацию. Я написал очень простой класс — Main.java.

Наш самый первый вызов — new FileSystemXmlApplicationContext. В качестве параметра мы передаем список файлов, в которых прописана конфигурация. Как вы уже догадались, файлов может быть несколько — если конечно вам это надо.
Важно: учтите, что путь до файла является относительным текущего каталога, даже если вы указали начальный «/».
После загрузки контекста вы просто обращаетесь к нему с просьбой дать бин по имени -(ProfessionFacade)context.getBean(«professionFacade»). Но здесь необходимо привести бин к нужному вам типу. Не думаю, что это проблема. В общем-то и все — ваш бин готов к работе. Можете посмотреть, что выводится на консоль. Также вы можете заменой одного символа поменять реализацию нашего DAO — просто меняем professionDAO_1 на professionDAO_2 у бина professionFacade и запускаем наше приложения снова. Пример находится в проекте Spring_01.

Ради эксперимента вы можете сделать два свойства у класса ProfessionFacade — для двух DAO. Одному присвоить значениеprofessionDAO_1, а второму professionDAO_2. Дальше читайте документацию и экспериментируйте с нашим примером. А мы перейдем к следующей возможности Spring — AOP.

Aspect Oriented Programming

В реализации Spring вы можете создать специальные классы, которые называются интерсепторы (interceptor). Их задача — сделать что-то вроде обертки вокруг вызова метода. Когда вы посмотрите на код нашего примера — вы достаточно его легко поймете.

ProfessionInterceptor.java

Как видите я не стал много придумывать — просто сделал два вывода на экран — до и после метода Object rval = invocation.proceed();. Если внимательно посмотреть в документации, что из себя представляет класс MethodInvocation, то можно найти у него метод getMethod, который в свою очередь возвращает объект типа java.lang.reflect.Method. А уж этот стандартный класс дает полную информацию о том, что и как в нашем методе происходит.

Теперь давайте обратим внимание каким образом мы конфигурируем наши классы в файле Spring. Смотрим обновленный StudentExample.xml.

Я убрал второй DAO из нашего файла, но добавил более сложные вещи. Первым я описал наш класс интресептора -students.dao.ProfessionInterceptor. Его код мы только что посмотрели. Далее я ввел специальный класс -professionFacadeProxy. По сути он представляет из себя копию методов того бина, который мы указали в качестве target — а именноprofessionFacade. В двух словах: Spring «на лету» создает свой собственный класс, который унаследует все методы своего target, но к тому же при вызове какого-либо метода «протаскивает» этот вызов через все интерсепторы, которые зарегистрированы. В нашем случае он всего один, но ради эксперимента можно скопировать строку дважды (трижды, …)

<value>professionInterceptor</value>

и посмотреть на результат. Осталось привести пример вызова нашего нового бина — professionFacadeProxy.

Пример находится в проекте Spring_02. Здесь важно отметить два момента:

  1. Мы теперь получаем бин по имени professionFacadeProxy. Но что еще более важно — приводим его к типу ProfessionFacade. Т.е. для нас он остается все тем же ProfessionFacade, хотя по сути это не совсем так
  2. Приведение к типу ProfessionFacade дает нам возможность работать с этим объектом, как с нашим изначальным классом

Теперь у нас появился замечательный механизм — мы описали интерсептор и указали в конфигурации какой класс и какими интерсепторами надо «оборачивать». Теперь по сути при входе в нужный нам метод мы можем что-то сделать перед его реальным исполнением и также сделать что-то после его окончания. В нашем случае мы сделали просто два вывода на экран. Но ведь можем сделать что-то более существенное. Например — открыть транзакцию и закрыть. О чем мы собственно и думали. А значит мы можем перейти к третьей части.

 

Работа с базами данных

Как мы видели раньше, Spring предоставляет нам практически все необходимое. Конечно же мы рассмотрели достаточно простой вариант работы интерсептора. Он может быть гораздо сложнее — мы можем указать методы, которые мы хотим «окружить заботой», можем указать не один, а несколько интерсепторов и т.д. Но опять повторюсь — данная статья не претендует на роль глубокого исследования возможностей Spring — я хочу показать основные возможности.

Если немного порассуждать, то мы придем к следующим выводам: нам нужен специальный интерсептор, который будет начинать транзакцию. Причем эта транзакция должна быть настраиваемая. Также нам нужен специальный класс, который умел бы работать с базой данных и одновременно умел бы использовать уже открытую транзакцию (которую нам открыл интерсептор). Еще лучше, если бы класс для работы с базой данных был совместим с широко распространенными пакетами типа Hibernate или TopLink. И вы наверняка не удивитесь, когда узнаете, что Spring имеет все необходимое.

Начнем мы с кода нашего главного класса — Main.java

Как вы можете видеть мы внесли несколько изменений, главным из которых является добавление еще одного конфигурационного файла -src/StudentDatabase.xml. Он гораздо интереснее, чем код класса Main.

Чуть позже мы рассмотрим схему взаимодействия всех наших классов. А пока рассмотрим все элементы по порядку:

  • studentDS — этот бин используется для создания коннекта к базе данных. Если посмотреть в документацию, то классDriverManagerDataSource является реализацией стандартного инерфейса javax.sql.DataSource. Реализация этого интерфейса создает пул коннектов к базе данных и может быть использована в системе JNDI какого-либо сервера
  • sessionFactory — этот класс используется для создания SessionFactory от Hibernate. Да-да, теперь мы обладатели класса, который умеет работать с Hibernate. Что не может не радовать. Причем обратите внимание, что мы тут описали аннотированный класс -students.entity.Profession а также указали параметры, которые используются для Hibernate — я имею в виду hibernate.dialect иhibernate.show_sql
  • txManager — этот класс умеет создавать транзакции Hibernate.
  • abstractTransactionProxy — это наш специальный интерсептор, который умеет открывать транзакции и закрывать их. В нужное для нас время. Важно обратить внимание на параметры тэга transactionAttributes. Они указывают, какие методы (точнее, какие маски) будут окружаться транзакциями. Причем надо отметить, что есть транзакции только для чтения (readOnly) и есть транзакции, которые будут реагировать на появление исключений — в случае генерации исключения транзакция будет отменена. Как вы наверняка догадались будут отменены все действия с базой данных, произошедшие внутри метода. Еще надо обратить внимание, что этот бин объявлен как абстрактный (смотрите атрибут abstract=»true»). Мы будем использовать этот бин как родителя для реальных наших фасадов. Увидим это чуть позже.
  • hibernateTemplate — это класс, который умеет выполянть запросы к базе данных через Hibernate

Итак, давайте попробуем увидеть работу всех этих компонентов в комплексе. Все начинается тогда, когда вы вызываете какой-то из методов нашего фасада — ProfessionFacade. Причем я еще раз прошу обратить внимание, что на самом деле из контекста Spring мы получаем не наш класс, а его расширение, которое уже имеет в своем составе интерсептор, позволяющий работать с базой данных. Расширенный класс своего рода посредник — он сделает необходимые действия до вызова нашего реального метода — откроет транзакцию, создаст объект Session для нашего объектаhibernateTemplate, после этого он вызовет уже наш метод, потом получит управление обратно и в зависимости от результата (было исключение или нет) подтвердит транзакцию или откатит. Этот бин имеет свойство transactionManager. Т.е. когда надо открывать/закрывать транзакцию, бин не делает это сам — он использует менеджер транзакций. А тот в своею очередь использует SessionFactory от Hibernate (в нашем случае). И использует именно потому, что мы используем пакет Hibernate. Использовали бы что-то другое, тогда и менеджер транзакций был бы иной. Например для работы с J2EE-сервером можно использовать JTA транзакции сервера.
Теперь в коде нашего ProfessionDAO мы используем свойство template для выполнения запросов к базе данных. Это свойство использует уже созданные бином abstractTransactionProxy транзакцию и сессию Hibernate.
Осталось только более внимательно посмотреть на бин sessionFactory. Думаю, что многие из свойств этого бина вам достаточно очевидны — мы используем dataSource, который определен — это параметры нашего коннекта. Также у нас есть список entity классов — в данном случае этоstudents.entity.Profession. Ну и под конец у нас есть параметры для подключения к MySQL — hibernateProperties.

 

Думаю, что еще немного внимательно посмотрев на все бины вы сложите себе картинку, как это все работает. Несколько слов надо посвятить второму файлу конфигурации — StudentExample.xml.

Здесь надо обратить внимание на следующий момент — конфигурация бина professionFacade. Во-первых в нем мы видим атрибут parent, в котором находится наш важный бин-посредник abstractTransactionProxy. И второй момент — мы инициализовали свойство dao для классаstudents.facade.ProfessionFacade сразу внутри описания. Вот в принципе и все. Остальной код для запуска есть в архиве (пример находится в проекте Spring_03) и я советую вам быстрее скачивать его, запускать и исследовать новые возможности, о которых вы узнали. В очередной раз призываю вас почитать документацию к Spring — это очень полезное занятие. В данной статье мы только прикоснулись к пакету, смогли понять самое начало. Дорогу осилит идущий. А теперь мы переходим к еще одной возможности Spring — шаблону MVC для Web. Добро пожаловать вЧасть 20 — Spring. Переходим на Web.

Более современный Spring

В исходник включен более современный вариант создания прокси вокруг фасада — его можно посмотреть в примере Spring_03a. Попробуйте разобраться в нем сами — там изменены только файлы конфигурации. Рассматривайте это в качестве самостоятельной работы. Единственное замечание — там потребуется еще одна библиотека — aspectjweaver.jar.
А в остальном — ставьте себе вопросы и ищите ответы. Удачи.

Архив с исходными кодами: Исходный код

4 comments to Spring. Бизнес-уровень в действии

  • Zakk  says:

    Попробуйте использовать org.springframework.orm.hibernate4.LocalSessionFactoryBean

  • Дима  says:

    Замечательный сайт! Спасибо за него большое!!!
    П.С. Пожалуй, стОит ограничить пользователям количество вводимого текста в комментариях: копипастить сюда логи ошибок компиляции — не комильфо.

  • Ильяс  says:

    Добрый день. Просьба подсказать, статья называется «Бизнес-уровень в действии». Мне показалось что тема так и не раскрыта. Вы пишите «Класс ProfessionView — класс для бизнес-уровня» хотя в этом классе у вас одни гетеры и сетеры (нет даже хотя бы одного if-а). Подскажите, так в каком классе должна располагаться бизнес-логика системы?

    • admin  says:

      Класс ProfessionFacade

Leave a reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.