АНДРЕЙ УВАРОВ, инженер-разработчик, интересы: веб-разработка на Java, паттерны проектирования, функциональное программирование, Lisp-подобные языки
Основы Spring
Фреймворк Spring заслуженно называют архитектурным клеем, так как на его основе строятся очень сложные, но тем не менее изящные приложения.
Spring – это фреймворк, который позволяет создавать модульные масштабируемые системы. Любой разработчик, непосредственно вовлечённый в процесс создания Enterprise-приложений на Java, просто обязан хорошо разбираться в технологии, ставшей стандартом де-факто в этой отрасли разработки ПО. Последняя стабильная версия имеет номер 2.5.6 и доступна на официальном сайте – http://www.springsource.org. Там же доступна очень подробная документация.
Структура
За время своего существования Spring оброс большим количеством функционала, выделяемого в отдельные модули. Давайте рассмотрим архитектуру этого фреймворка. На рис. 1 изображены модули, которые в совокупности и называются Spring Framework.
Рисунок 1. Структура
Core – ядро системы, реализованное в виде IoC-контейнера;
ORM (Object-Relationship mapping) – модуль, предоставляющий средства для взаимодействия с наиболее популярными ORM-фреймворками[1], как JPA, JDO, Hibernate и другими;
DAO (Data Access Objects) – данный модуль предоставляет возможности управления транзакциями (включая декларативное управление). Также включает в себя набор шаблонов для работы с JDBC;
JEE (Java Enterprsie Edition) – часть системы, которая реализует взаимодействие с технологиями, обобщёнными названием JEE. Вот только некоторые из них: JMS, JMX, JCA, EJB;
AOP (Aspec-Oriented Programming) – реализация Spring AOP и поддержка @AspectJ. AOP даёт нам дополнительные возможности физического разделения кода с целью сделать код более структурированным[2];
WEB – модуль, содержащий средства для взаимодействия с популярными веб-фреймворками (Struts, Tapestry), шаблонизаторами (Velocity, Freemarker) и многими другими полезными в веб-разработке вещами. Также включает в состав Spring MVC – собственную реализацию паттерна Model-View-Controller.
Core – теория
Все составные части прочно держатся на одной основе – core (ядре), которое представляет собой так называемый IoC-контейнер. IoC расшифровывается как Inversion of Control и является паттерном проектирования. Суть IoC заключается в уменьшении связности кода. Примером является паттерн Dependency Injection, следуя которому зависимости в нашем коде строятся на интерфейсах и абстрактных классах. И уже в ходе выполнения кода фреймворк берёт всю работу по инстанцированию на себя, следуя заранее определённым нами правилам.
В качестве примера можно привести следующую ситуацию: мы программируем действия вывода на печать, оперируя неким интерфейсом PrintService с единственным методом void print(String message), не задумываясь о том, какая реализация будет использована в ходе выполнения. А затем при помощи Spring мы конфигурируем свой код таким образом, что в некоторых случаях будет использована реализация, выводящая сообщения на экран, в других – на принтер и т.д. Так, если ещё нет никаких реализаций PrintService, то на время написания и тестирования своего кода мы можем создать реализацию MockPrintService, которая будет выводить сообщения в отладочный лог-файл. А в момент, когда другой разработчик создаст нужную реализацию PrintService, мы без каких-либо изменений в java-коде легко сможем переключиться на неё.
Dependency Injection позволяет нам упростить код. Упрощение кода даёт выигрыш в скорости его разработки и простоте поддержки. Чем проще код, тем легче его тестировать, что также является плюсом. Положительным моментом является и то, что код не будет зависеть от фреймворка, что увеличивает его переносимость.
Это и есть теоретический минимум, необходимый для дальнейшего постижения Spring.
Core – практика
А теперь разберём очень простой и очень традиционный пример «Hello World».
Начнём с построения интерфейса:
package com.andrewdashin.examples.spring.beans;public interface HelloBean { public void helloWorld();}
Создадим реализацию нашего интерфейса:
package com.andrewdashin.examples.spring.beans;public class HelloBeanImpl implements HelloBean { String hello; public void helloWorld() { System.out.println(hello); } public void setHello(String hello) { this.hello = hello; }}
Следующим шагом определим правила, по которым будут создаваться объекты в нашем приложении, файл context.xml:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean id="helloBean" class="com.andrewdashin.examples.spring.beans.HelloBeanImpl"> <property name="hello"> <value>Hello World!</value> </property> </bean></beans>
Теперь непосредственно код приложения:
package com.andrewdashin.examples.spring;import com.andrewdashin.examples.spring.beans.*;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("resources/beans.xml"); HelloBean bean = (HelloBean) context.getBean("helloBean"); bean.helloWorld(); }}
Добавим файл manifest:
Manifest-Version: 1.0
Created-By: 1.5.0_13 (Apple Inc.)
Main-Class: com.andrewdashin.examples.spring.Main
Class-Path: lib/spring.jar lib/commons-logging-1.1.jar
Как вы, наверное, уже заметили, необходимо создать в корневом каталоге проекта папку lib и поместить туда spring.jar и commons-logging-1.1.jar. Дело в том, что Spring использует большое количество сторонних библиотек, и в частности – Apache Commons Logging. В связи с этим Spring Framework доступен в виде двух дистрибутивов: только фреймворк и фреймворк вместе со всеми зависимостями. Если при скачивании был выбран второй вариант, то необходимый jar-архив можно найти в дистрибутиве, иначе – на официальном сайте http://commons.apache.org/logging.
Компилируется наш пример следующим образом:
javac -cp lib/spring.jar -d ./src/com/andrewdashin/examples/spring/Main.java /
src/com/andrewdashin/examples/spring/beans/HelloBean.java /
src/com/andrewdashin/examples/spring/beans/HelloBeanImpl.java
Запакуем в jar:
jar cvfm main.jar MANIFEST.MF com/andrewdashin/examples/spring/Main.class /
com/andrewdashin/examples/spring/beans/HelloBean.class /
com/andrewdashin/examples/spring/beans/HelloBeanImpl.class resources/context.xml
И финальная стадия – практически проверим работу приложения (см. рис. 2).
Рисунок 2. Результат
Кроме ожидаемого результата, на экране появилось много дополнительной информации. Эта информация есть не что иное, как лог, ведущийся фреймворком и помогающий понять, что в данный момент происходит. Возможности логирования очень легко настраиваются под любые ситуации.
Конечно, для создания HelloWorld-приложений Spring не лучший выбор, т.к. является в данном случае весьма избыточным. Но чем больше кода в системе, тем более полезен данный фреймворк.
Вернёмся к нашему примеру. Итак, интерфейс и его реализация чрезвычайно примитивны, в связи с чем удостоим нашего внимания файл context.xml. Данный файл является описанием контекста приложения. В секции beans есть единственное определение bean с некоторыми свойствами. Bean – есть сущность, которая определяет правила создания экземпляров объектов. Параметр id определяет уникальный идентификатор, по которому будут создаваться экземпляры объектов, класс которых определён параметром class. Определяя свойство hello, мы заставляем Spring при каждом создании бина вызывать метод setHello(), передавая в качестве аргумента указанное нами строковое значение «Hello World!». Аргументами могут быть как значения, так и ссылки на другие бины.
Входной точкой нашего приложения является класс Main. Spring-контекст создаётся следующим образом:
new ClassPathXmlApplicationContext("resources/context.xml")
Контекстом является класс-фабрика, в данном случае ClassPathXmlApplicationContext, которая, руководствуясь заданной конфигурацией, создаёт необходимые нам объекты.
В приведённом примере экземпляр helloBean мы получали с уже автоматически заполненным свойством hello. В общем случае свойство hello будет являться классом, который в свою очередь также нужно будет инстанцировать.
Обычно в приложениях множество объектов зависят друг от друга. В качестве примера рассмотрим фрагмент конфигурации контекста:
<bean id="abstractController" abstract="true"> <property name="commandClass" value="org.example.Command"/></bean><bean id="requestDao" class="org.example.dao.RequestDaoImpl"> <constructor-arg index="1" value="org.example.domain.RequestEntity"/></bean><bean id="requestService" class="org.example.service.RequestServiceImpl"> <property name="requestDao" ref="requestDao"/></bean><bean id="someRequestController" class="org.example.mvc.Controller" parent="abstractController"> <property name="requestService" ref="requestService"/></bean><bean id="otherRequestController" class="org.example.mvc.OtherController" parent="abstractController"> <property name="requestService" ref="requestService"/></bean>
Чтобы создать экземпляр someRequestController, необходимо прежде создать requestService, который в свою очередь зависит от requestDao. Порой графы таких зависимостей могут достигать достаточно больших размеров. И сложно представить, как можно было бы справиться вручную с созданием одного простого класса, если бы он зависел от множества других.
В примере использованы два типа удовлетворения зависимостей (Dependency Injection): посредством set-методов и при помощи конструкторов. Так, если мы пишем:
<property name="requestDao" ref="requestDao"/>
то Spring ищет метод с именем setRequestDao и пробует его выполнить, передавая в качестве аргумента указанное значение.
В случае с конструктором – вызывается конструктор класса, передавая аргументы, в указанном параметром index порядке. Если есть необходимость, то можно использовать сразу оба метода для конфигурирования одного объекта. В примере мы определили бин abstractController как абстрактный, а также определили для него свойство. Абстрактные бины в Spring выполняют функцию обобщения. Например, если у вас есть несколько бинов, которые имеют одинаковые зависимости (и могут быть абсолютно разными классами, как в случае с someRequestController и otherRequestController), то, вынося общие свойства в абстрактный бин, мы можем уменьшить повторение кода в конфигурации контекста.
Если скрупулёзно изучить код во время выполнения, то может обнаружиться, что someRequestController и otherRequestController используют один и тот же экземпляр класса requestService. То есть фреймворк один раз создал requestService, а когда появилась необходимость в повторном создании, он просто вернул уже существующий экземпляр.
Дело в том, что Spring реализует несколько способов создания бинов:
Синглтон – способ по умолчанию. Создаётся и используется в дальнейшем только один объект. Эта возможность является весьма полезной, так как помогает избежать реализации шаблона проектирования singleton в Java-коде (что упрощает написание тестов).
Прототип – каждый раз создаётся новый объект.
Запрос, сессия и глобальная сессия – эти способы используются в веб-приложениях. Будет создаваться лишь один экземпляр на каждый http-запрос или сессию.
Экземпляры всех бинов, объявленных как синглтон (явным или неявным образом), создаются на этапе инициализации контекста, что иногда может привести к существенному замедлению запуска приложения. Для подобных случаев предусмотрена возможность «ленивой» инициализации, т.е. бины будут инициализироваться лишь тогда, когда появится необходимость в их использовании.
***
В данной статье вы познакомились лишь с малой частью Spring, с его основами. Но даже используя только их, можно выйти на новую ступень разработки кода, более гибкого, переносимого, легко тестируемого (и как следствие, имеющего большее покрытие тестами) и без дополнительных затрат стратегически важного ресурса – времени.