Продолжается подписка на наши издания! Вы не забыли подписаться?

Spring Framework

Автор: Род Джонсон (Rod Johnson)

Еще один framework?
Архитектурные достоинства Spring
Что же делает Spring?
Постановка задачи
Контейнер с инверсным управлением
Пример XmlBeanFactory
JDBC-абстракция и иерархия исключений при доступе к данным
Интеграция с ORM
Управление транзакциями
АОП
Web-фреймворк MVC
Реализация EJB
Использование EJB
Тестирование
Кто использует Spring?
Итоги

Возможно, вы уже слышали о такой штуке как Spring Framework. В этой статье я постараюсь объяснить, зачем он нужен, и как может помочь в разработке J2EE-приложений.

Еще один framework?

Вы можете сказать "еще один Framework". К чему бы вам заниматься чтением этой статьи и скачивать Spring Framework, если уже есть масса как open source, так и проприетарных J2EE Framework-ов?

Но у меня есть несколько причин считать, что Spring уникален:

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

Несмотря на то, что Spring разрабатывался как open source-проект только с февраля 2003 года, у него есть долгое наследие. Open source-проект начался с кода инфраструктуры, опубликованной в книге Expert One-on-One J2EE Design and Development в конце 2002 года. Expert One-on-One J2EE тоже лег в основу архитектурных размышлений о Spring. Но сами архитектурные концепции восходят к началу 2000 года, и отражают мой опыт в разработке инфраструктуры для нескольких удачных коммерческих проектов.

С января 2003 года Spring размещен на SourceForge. В проекте участвует десять разработчиков, шестеро из которых очень активны.

Архитектурные достоинства Spring

Прежде чем перейти к особенностям, посмотрим на достоинства, привносимые Spring в проект:

Spring позволяет реализовать простейшее решение проблемы из возможных. А это многого стоит.

Что же делает Spring?

Spring предоставляет широкую функциональность, основные направления которой я сейчас вкратце опишу.

Постановка задачи

Сперва стоит определить круг деятельности Spring. Spring рассчитан на широкую область применения, но следует ясно представлять, где он применим, а где – нет.

Главная цель Spring – упростить использование J2EE и способствовать хорошему программированию.

Spring не рассчитан на повторное изобретение колеса. Поэтому вы не найдете в Spring пакетов для ведения логов, пулов подключений, или координаторов распределенных транзакций. Все это либо можно найти в open source-проектах (например, Commons Logging, который мы вовсю используем для логирования, или Commons DBCP), либо предоставляется выбранным сервером приложений. По ряду причин мы не предоставляем и ORM-слой. Хорошими решениями этой проблемы служат Hibernate и JDO.

Spring нацелен на упрощение работы с уже существующими технологиями. Например, хотя мы и не занимаемся низкоуровневой координацией транзакций, мы предоставляем слой абстракции через JTA или любую другую транзакционную стратегию.

Spring не конкурирует напрямую с другими open source-проектами, пока мы не чувствуем, что способны предложить нечто новое. Например, как и многие разработчики, мы никогда особо не радовались Struts-у, и чувствуем, что есть место улучшениям в MVC Web Framework. В некоторых областях, таких, как легковесные IoC-контейнеры или АОП-фреймворки, у Spring есть прямые конкуренты, но есть и такие направления, где просто нет популярных решений (Spring – пионер в этих областях).

Spring переносим между разными серверами приложений. Конечно, обеспечение переносимости – это всегда вызов, но мы избегаем всего платформно-зависимого или нестандартного, и поддерживаем WebLogic, Tomcat, Resin, JBoss, WebSphere и другие серверы приложений.

Контейнер с инверсным управлением

Ядром дизайна Spring является пакет org.springframework.beans, предназначенный для работы с JavaBeans. Этот пакет обычно не используется напрямую, а служит основой для большей части другой функциональности.

Следующий слой абстракции – это "Bean Factory". Spring bean factory – это обобщенная фабрика, позволяющая получать объекты по имени, и способная управлять отношениями между объектами.

Bean factorу поддерживает два вида объектов:

Поскольку org.springframework.beans.factory.BeanFactory – это простой интерфейс, он может быть реализован для различных нижележащих методов хранения. Вы легко можете реализовать свой собственный, хотя это и мало кому нужно. Наиболее широко используемые определения BeanFactory:

Каждое bean-определение может быть как POJO (определяемое именем класса и свойствами инициализации JavaBean), так и FactoryBean. Интерфейс FactoryBean добавляет уровень indirection. Обычно это используется для создания proxied-объектов с использованием АОП или других подходов: например, прокси, использующих декларативное управление транзакциями (это концептуально похоже на EJB-перехват, но на практике гораздо проще).

BeanFactories могут участвовать в иерархиях, "наследуя" определения своих предков. Это позволяет целому приложению разделять общую конфигурацию, в то время как индивидуальные ресурсы, такие, как сервлеты-контроллеры также имеют собственный независимый набор объектов.

Такая мотивировка использования JavaBeans описана в 4 главе моей книги Expert One-on-One J2EE Design and Development, доступной бесплатно на ServerSide (http://www.theserverside.com/articles/article.tss?l=RodJohnsonInterview).

Благодаря концепции BeanFactory Spring является IoC (Inversion of Control, инверсия управления) контейнером. Я не люблю термина контейнер, поскольку он вызывает в памяти тяжеловесные, например, EJB-контейнеры. BeanFactory в Spring – это контейнер, который может быть создан одной строкой кода, и не требует никаких особых мероприятий для развертывания.

Концепция, лежащая в основе инверсии управления, часто выражается "голливудским принципом": "Не звоните мне, я вам сам позвоню". IoC переносит ответственность за выполнение действий с кода приложения на фреймворк. В отношении конфигурирования это означает, что если в традиционных контейнерных архитектурах наподобие EJB, компонент может вызвать контейнер и спросить: "где объект Х, нужный мне для работы?", то в IoC сам контейнер выясняет, что компоненту нужен объект Х, и предоставляет его компоненту во время исполнения. Контейнер делает это, основываясь на подписях методов (таких, как свойства JavaBean) и, возможно, конфигурационных данных в формате XML.

У IoC есть несколько важных преимуществ. Например:

Последний пункт заслуживает особого внимания. IoC не похож на традиционные контейнерные архитектуры типа EJB, именно этой минимизацией зависимости кода приложения от контейнера. Это значит, что бизнес-объекты потенциально могут работать в разных IoC-фреймворках – или вне любого фреймворка – без изменения кода.

IoC – это не новая концепция, хотя в J2EE-сообществе она только сейчас выходит на первый план. Существуют альтернативные IoC-контейнеры: например, Apache Avalon, PicoContainer и HiveMind. Avalon так и не набрал достаточной популярности, несмотря на мощность и долгую историю. Он довольно сложен и тяжеловесен, а также навязчивее, чем более новые реализации. PicoContainer легковесен и делает ударение на выражении зависимостей через конструкторы, а не свойства JavaBean. В отличие от Spring, его дизайн позволяет определять только один объект каждого типа (возможно, это ограничение вытекает из отказа от использования любых метаданных, хранящихся вне Java-кода).

BeanFactories в Spring крайне легковесны. Пользователи успешно применяли их в апплетах и отдельных Swing-приложениях (в EJB-контейнерах они также хорошо работают). Нет никаких особых шагов развертывания и никакого существенного времени запуска. Такая возможность мгновенного создания экземпляра контейнера на любом уровне приложения может быть очень ценной.

Концепция BeanFactories используется в Spring повсеместно, и является основным объяснением внутренней непротиворечивости Spring. Spring уникален среди IoC-контейнеров еще и тем, что использует IoC как базовую концепцию всего полнофункционального фреймворка.

Для разработчиков приложений наиболее важно то, что одна или несколько BeanFactory обеспечивают хорошо разработанный слой бизнес-объектов приложения. Это аналогично слою local session beans (но гораздо проще). В отличие от EJB, объекты этого слоя могут быть взаимосвязанными, а их отношениями управляет владеющая ими фабрика. Наличие хорошо разработанного слоя бизнес-объектов очень важно для успеха архитектуры.

Spring ApplicationContext – это субинтерфейс BeanFactory, предоставляющий поддержку для:

Пример XmlBeanFactory

Пользователи Spring обычно конфигурируют приложения посредством XML-файлов "bean-определений". Корнем такого XML-документа служит элемент <beans>. Этот элемент содержит одно или несколько определений <bean>. В каждом определении обычно указывается класс и свойства. Нужно также указать id – имя, под которым этот bean будет использоваться в коде.

Вот простой пример конфигурирования трех объектов приложения с отношениями, обычно встречающимися в J2EE-приложениями:

В следующем примере используется BasicDataSource из проекта Jakarta Commons DBCP. Этот класс (как и многие другие существующие классы) можно с легкостью использовать в BeanFactory Spring, поскольку он конфигурируется как JavaBean. Метод close, который требуется вызвать при завершении работы, может быть зарегистрирован через атрибут "destroy-method", чтобы избавить BasicDataSource от необходимости реализовать какой-либо Spring-интерфейс.

<beans>

  <bean id="myDataSource"
        class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
    <property name="driverClassName">
      <value>com.mysql.jdbc.Driver</value>
    </property>
    <property name="url">
      <value>jdbc:mysql://localhost:3306/mydb</value>
    </property>
    <property name="username"><value>root</value>
    </property>
  </bean>

Все интересующие нас свойства BasicDataSource имеют тип Strings, поэтому их значения указываются в элементе <value>. Spring использует стандартный механизм JavaBean PropertyEditor для преобразования при необходимости представления String в другие типы.

Теперь определим DAO, ссылающийся на DataSource. Отношения между bean-ами указываются с помощью элемента <ref>:

<bean id="exampleDataAccessObject" class="example.ExampleDataAccessObject">
  <property name="dataSource">
    <ref bean="myDataSource"/>
  </property>
</bean>

Бизнес-объект имеет ссылку на DAO и свойство int (exampleParam):

<bean id="exampleBusinessObject" class="example.ExampleBusinessObject">
  <property name="dataAccessObject"><ref bean="exampleDataAccessObject"/></property>
  <property name="exampleParam"><value>10</value></property>
</bean>

</beans>

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

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

Такая возможность аналогична имеющейся в PicoContainer.

Как можно использовать Autowire, если нет желания явно задавать отношения в коде, показано в следующем примере:

<bean id="exampleBusinessObject"
      class="example.ExampleBusinessObject"
      autowire="byType">
  <property name="exampleParam">
    <value>10</value>
  </property>
</bean>

При этом Spring выяснит, что свойство dataSource объекта exampleBusinessObject должно указывать на реализацию DataSource в текущей BeanFactory. Если bean такого типа отсутствует в текущей BeanFactory, или если их более одного – это ошибка. По-прежнему нужно задавать свойство exampleParam, поскольку это не ссылка.

Вынос отношений из Java-кода имеет огромные преимущества перед жестким кодированием, поскольку позволяет изменять XML-файлы, не трогая ни строчки кода. Например, можно изменить ссылку на класс в bean-определении myDataSource для использования альтернативного пула подключений или тестового источника данных. Можно, например, чуть-чуть изменив XML, использовать Spring FactoryBean – JNDI-локатор – чтобы получить источник данных от сервера приложений.

Теперь посмотрим на пример Java-кода бизнес-объекта. Обратите внимание, что в приведенном ниже коде нет никаких Spring-зависимостей. В отличие от EJB-контейнера, BeanFactory Spring ненавязчив: в объектах приложений его обычно не приходится упоминать.

public class ExampleBusinessObject implements MyBusinessObject 
{
  private ExampleDataAccessObject dao;
  private int exampleParam;

  public void setDataAccessObject(ExampleDataAccessObject dao) 
  {
    this.dao = dao;
  }

  public void setExampleParam(int exampleParam) 
  {
    this.exampleParam = exampleParam;
  }

  public void myBusinessMethod() 
  {
    // Использование DAO
  }
}

Обратите внимание на методы, устанавливающие значения свойств (setter-ы). Они вызываются Spring при инициализации объекта в соответствии со ссылками, описанными в XML документе определения bean.

Такие bean-ы приложений не нуждаются в зависимости от Spring. Они не должны реализовать никакие интерфейсы или расширять классы Spring: им нужно всего лишь соблюдать соглашения об именовании JavaBeans. Их легко использовать вне Spring-приложения, например, в тестовой среде. Просто создайте их экземпляры с конструкторами по умолчанию и задайте свойства вручную, с помощью вызовов setDataSource() и setExampleParam(). При использовании конструкторов без аргументов вы также можете определять любое количество других конструкторов с любым количеством аргументов.

Обратите внимание, что свойства JavaBean не объявляются в бизнес-интерфейсе, с которым будет работать вызывающая сторона. Это детали реализации. Можно подключить различные реализующие классы с разными bean-свойствами, не влияя на подключенные объекты или вызывающий код.

Конечно, у XML BeanFactory в Spring гораздо больше возможностей, чем описано здесь, но идею вы уже поняли. Кроме простых свойств, а также свойств, для которых годится JavaBeans PropertyEditor, Spring может автоматически обрабатывать списки, ассоциативные массивы и java.util.Properties.

Bean-фабрики и контексты приложений обычно ассоциируются с областью видимости, определяемой J2EE-сервером:

Эти приемы, предоставляемые спецификацией J2EE, в целом исключают необходимость использования синглтонов для самозагрузки BeanFactory.

Однако создать экземпляр BeanFactory программно, если нужно, несложно. Например, ее можно создать и получить ссылку на определенный выше бизнес-объект за три строчки кода:

InputStream is = getClass().getResourceAsStream("myFile.xml");
XmlBeanFactory bf = new XmlBeanFactory(is);
MyBusinessObject mbo = (MyBusinessObject) bf.getBean("exampleBusinessObject");

Этот код будет работать вне сервера приложений: он даже не зависит от J2EE, так как Spring IoC-контейнер – это "pure Java".

JDBC-абстракция и иерархия исключений при доступе к данным

Доступ к данным – это еще одна козырная масть Spring.

JDBC предлагает красивую абстракцию нижележащей БД, но имеет неудобный API. Вот некоторые из проблем:

Spring подходит к этим проблемам с двух сторон:

Spring предоставляет два уровня JDBC API. Первый, в пакете org.springframework.jdbc.core, использует обратные вызовы для переноса управления – а, следовательно, обработки ошибок, получения и освобождения подключений – из кода приложения во фреймворк. Это еще один тип инверсии управления, не менее ценный, чем используемый в управлении конфигурацией.

Похожий подход (использование обратных вызовов) Spring использует при работе с еще несколькими API для получения и освобождения ресурсов, такими, как JDO (получение и освобождение PersistenceManager), управление транзакциями (с использованием JTA) и JNDI. Классы Spring, выполняющие такие обратные вызовы, называются шаблонами (templates).

Например, объект JdbcTemplate в Spring может быть использован для выполнения SQL-запроса и сохранения результата в списке:

JdbcTemplate template = new JdbcTemplate(dataSource);
final List names = new LinkedList();
template.query("SELECT USER.NAME FROM USER",
  new RowCallbackHandler() 
  {
    public void processRow(ResultSet rs) throws SQLException 
    {
      names.add(rs.getString(1));
    }
  });

Заметьте, что код приложения внутри обратного вызова может выдать SQLException: Spring перехватит любые исключения и переадресует их в свою собственную иерархию. Разработчик может сам выбирать, какие исключения перехватывать и обрабатывать.

JdbcTemplate предоставляет ряд методов для поддержки разных сценариев, включая подготовленные выражения и пакетное обновление. Абстракция JDBC в Spring дает очень небольшое снижение производительности по сравнению со стандартным JDBC, даже в случае приложений, работающих с большими выборками.

Абстракция JDBC более высокого уровня находится в пакете org.springframework.jdbc.object. Он построен на основе функциональности обратных вызовов JDBC, но предоставляет API, в котором операции с РСУБД – запросы, обновления или хранимые процедуры – упаковываются в Java-объекты. Создание этого API было частично инспирировано API запросов в JDO, которое я счел интуитивным и очень практичным.

Объект запроса, возвращающий объекты User, может выглядеть так:

class UserQuery extends MappingSqlQuery
{
  public UserQuery(DataSource datasource)
  {
    super(datasource, "SELECT * FROM PUB_USER_ADDRESS WHERE USER_ID = ?");
    declareParameter(new SqlParameter(Types.NUMERIC));
    compile();
  }

  // Отображение строки выборки на Java-объект

  protected Object mapRow(ResultSet rs, int rownum) throws SQLException
  {
    User user = new User();
    user.setId(rs.getLong("USER_ID"));
    user.setForename(rs.getString("FORENAME"));
    return user;
  }

  public User findUser(long id)
  {
    // Использование метода суперкласса для обеспечения строгой типизации
    return (User)findObject(id);
  }
}

Это класс можно использовать так:

User user = userQuery.findUser(25);

Такие объекты часто являются внутренними классами в DAO. Они потокобезопасны, если только субкласс не делает чего-нибудь странного.

Еще один важный класс из пакета org.springframework.jdbc.object – это класс StoredProcedure. Spring позволяет замещать хранимую процедуру Java-классом с единственным бизнес-методом. Если пожелаете, вы можете определить интерфейс, который реализует хранимая процедура, то есть освободить код приложения от использования хранимых процедур вообще.

Иерархия исключений Spring при доступе к данным основана на неконтролируемых (unchecked) (времени исполнения) исключениях. Поработав со Spring над несколькими проектами, я все больше и больше убеждаюсь, что это было правильным решением.

Исключения при доступе к данным, как правило, не восстановимы. Например, если не вышло подключиться к БД, бизнес-объект вряд ли сможет обойти эту проблему. Потенциальными исключениями могут быть нарушения оптимистических блокировок, но не все приложения используют оптимистические блокировки. Как правило, плохо, если приходится писать код перехвата фатальных исключений, которые не могут быть осмысленно обработаны. Обычно лучше позволить им дойти до обработчиков верхнего уровня, типа сервлета или EJB-контейнера. В Spring все такие исключения являются подклассами DataAccessException, так что если хочется перехватывать все исключения, это делается с легкостью.

Заметьте, что если мы хотим восстановиться после неконтролируемого исключения при доступе к данным, мы можем это сделать. Можно написать код для обработки только подлежащих восстановлению исключений. Например, если принять решение, что восстановимы только нарушения оптимистических блокировок, можно написать следующий код в Spring DAO:

try 
{
  // выполняем работу
}
catch (OptimisticLockingFailureException ex) 
{
  // Разбираемся с конкретной ошибкой
}

Если бы Spring использовал контролируемые исключения, пришлось бы писать следующий код:

try 
{
  // выполняем работу
}
catch (OptimisticLockingFailureException ex) 
{
  // Разбираемся с конкретной ошибкой
}
catch (DataAccessException ex) 
{
  // Неизбежна обработка ошибки; повторная генерация исключения
}

Потенциальное возражение к первому примеру – компилятор не может принудительно обработать потенциально восстановимое исключение – применимо и ко второму случаю. Поскольку мы вынуждены отлавливать базовое исключение (DataAccessException), компилятор не задействует проверку подкласса (OptimisticLockingFailureException). Таким образом, компилятор заставит писать код для обработки неразрешимых проблем, но никак не поможет в борьбе с решаемыми.

Использование неконтролируемых исключений при доступе к данным в Spring совпадает с практикой многих – возможно, большинства – удачных фреймворков (частично это инспирировано JDO). JDBC – одно из немногих API доступа к данным, использующих контролируемые (checked) исключения. TopLink и JDO, например, используют только неконтролируемые исключения. Gavin King сейчас считает, что в Hibernate тоже следует выбрать неконтролируемые исключения.

Spring JDBC может помочь вам в нескольких вопросах:

На практике это выливается в существенный выигрыш в продуктивности программиста и уменьшение количества багов. Я всегда с отвращением брался за JDBC-код, а теперь я могу сфокусироваться на SQL, который нужно выполнить, вместо второстепенного управления ресурсами в JDBC.

Абстракцию JDBC Spring можно использовать отдельно – никто не заставляет использовать Spring целиком.

Интеграция с ORM

Конечно, часто хочется использовать объектно-реляционное отображение (Object/Relation Mapping, ORM) вместо обращения к реляционным данным. Приличный фреймворк должен это поддерживать. Поэтому Spring интегрируется с Hibernate 2.x и JDO. Его архитектура доступа к данным позволяет интеграцию с любой нижележащей технологией доступа к данным. Особенно хорошо интегрируются Spring и Hibernate.

Почему лучше использовать Hibernate плюс Spring, а не просто Hibernate? Потому что Spring дает существенные преимущества:

Управление транзакциями

Абстрагирования API доступа к данным недостаточно, нужно иметь в виду и управление транзакциями. JTA – очевидное решение, но достаточно громоздкое для прямого использования. В результате у многих J2EE-разработчиков есть ощущение, что EJB CMT – единственный разумный выбор для управления транзакциями.

Spring предоставляет собственную абстракцию управления транзакциями. Spring использует ее для:

Абстракция транзакций в Spring уникальна в том, что не привязана к JTA или любой другой технологии управления транзакциями. Spring использует концепцию стратегии транзакций, которая отделяет код приложения от нижележащей инфраструктуры транзакций (такой, как JDBC).

Почему это должно вас волновать? Разве JTA не является лучшим решением для любого управления транзакциями? Если вы пишете приложение, использующее только одну БД, вам не нужна вся сложность JTA. Вам не нужны ни XA-транзакции, ни двухфазная фиксация. Вам может даже не понадобиться мощный сервер приложений, обеспечивающий все это. Но, с другой стороны, вы же не хотите переписывать код, если придется работать с разными источниками данных?

Предположим, что вы решили избавиться от JTA, напрямую используя транзакции JDBC или Hibernate. Если вам придется работать с несколькими источниками данных, вы вынуждены будете похоронить весь код управления транзакциями и заменить его JTA-транзакциями. Это не слишком заманчивая перспектива, заставляющая большинство авторов, пишущих о J2EE (включая меня), рекомендовать повсеместное использование JTA-транзакций. А вот абстракция транзакций Spring позволяет перенастроить Spring на использование JTA вместо JDBC или Hibernate, сменили стратегию танзакций – и готово. Это изменение конфигурации, а не кода. Таким образом, Spring позволяет писать приложения, масштабируемые как вверх, так и вниз.

АОП

АОП-решения таких корпоративных вопросов как управление транзакциями, ранее решавшихся средствами EJB, с недавних пор вызывают значительный интерес.

Главная цель поддержки АОП в Spring – предоставление J2EE-сервисов POJO. Это схоже с целями JBoss 4. Однако АОП в Spring обладает преимуществом переносимости между серверами приложений, исключающим риск привязки к производителю. Spring работает как с Web, так и с EJB-контейнерами, и успешно применяется с WebLogic, Tomcat, JBoss, Resin, Jetty, Orion и многими другими серверами приложений и Web-контейнерами.

АОП в Spring поддерживает перехват методов. Основные поддерживаемые концепции АОП:

Spring поддерживает как stateful (один экземпляр на объект) так и stateless-перехватчики (один экземпляр для всех advice-ов).

Spring не поддерживает перехват полей. Это продуманное решение. Я всегда чувствовал, что перехват полей нарушает инкапсуляцию. Я предпочитаю думать об АОП как о парадигме, дополняющей ООП, а не конфликтующей с ним. Я не удивлюсь, если через пять-десять лет мы продвинемся намного дальше в постижении АОП, и оно займет достойное место в дизайне приложений (но для этого такие языковые решения как AspectJ должны стать куда привлекательнее, чем сейчас).

Spring реализует АОП, используя динамические прокси (там, где есть интерфейсы) или генерирование байт-кода во время исполнения (что позволяет создавать прокси классов). Вместе эти подходы работают в любом сервере приложений.

Spring – это первый АОП-фреймворк, реализующий интерфейсы AOP Alliance (www.sourceforge.net/projects/aopalliance). Это попытка определить интерфейсы, обеспечивающие взаимодействие перехватчиков между АОП-фреймворками.

На сайте TheServerSide и в других местах все еще продолжаются споры, является ли такой вид перехвата "истинным АОП". Меня не очень волнует, как это называется, если это может быть полезно на практике. Мне сойдет и название "декларативное middleware." АОП в Spring можно рассматривать как простую, легковесную альтернативу локальным stateless session beans (SLSB), устраняющим потребность в монолитном EJB-контейнере, и позволяющим создать собственный контейнер с теми сервисами, которые нужны.

Поскольку Spring подключает adviсe-ы к конкретным экземплярам объектов, а не на уровне классов, можно использовать разные экземпляры одного класса с разными advice-ами, или использовать экземпляры, к которым не применяются advice-ы, наряду с теми, к которым они применены.

Возможно, наиболее распространенная область использования Spring АОП – декларативное управление транзакциями. Оно построено на абстракции TransactionTemplate, описанной выше, и может обеспечить декларативное управление транзакциями для любого POJO. В зависимости от стратегии транзакций, нижележащим механизмом может быть JTA, JDBC, Hibernate или любое другое API, предлагающее управление транзакциями.

Декларативное управление транзакциями в Spring похоже на EJB CMT за исключением следующего:

Разумеется, можно использовать АОП Spring и для реализации специфичных для приложения аспектов. Делать это или нет - зависит больше от уровня владения концепциями АОП, чем от возможностей Spring, но это может оказаться очень полезным. Встречавшиеся нам примеры удачного использования включают:

Специфичные для приложений аспекты могут быть мощным способом удаления повторяющегося кода.

АОП Spring прозрачно интегрируется с концепцией BeanFactory. Код, получающий объект от BeanFactory не нуждается в знании о наличии advice-ов. Как и в случае любого объекта, контракт будет определен интерфейсами, реализуемыми объектом.

Следующий отрывок XML показывает, как определить АОП-прокси:

<bean id="myTest" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="proxyInterfaces">
    <value>org.springframework.beans.ITestBean</value>
  </property>
  <property name="interceptorNames">
    <list>
      <value>txInterceptor</value>
      <value>target</value>
    </list>
  </property>
</bean>

Обратите внимание, что класс определения bean-а всегда ProxyFactoryBean из АОП-фреймворка, хотя тип bean-а, используемый в ссылках или возвращаемый методом BeanFactory.getBean(), будет зависеть от интерфейсов прокси (поддерживаются множественные методы прокси). Свойство "interceptorNames" ProxyFactoryBean принимает список строк (нужно использовать имена bean-ов, а не ссылки, так как могут понадобиться новые экземпляры stateful-перехватчиков, если прокси относится к определению "прототипа", а не синглтона). Имена в этом списке могут принадлежать перехватчикам или pointcut-ам (перехватчик с информацией о том, когда его следует применять). Значение "target" в приведенном выше списке автоматически создает перехватчик, оборачивающий целевой объект. Это имя bean-а в фабрике, реализующей прокси-интерфейс. Bean myTest в этом примере может быть использован в BeanFactory как и любой другой. Например, другие объекты могут ссылаться на него через элементы <ref>, и эти ссылки будут заданы Spring IoC.

Возможно также программное создание АОП-прокси без использования BeanFactory, хотя это и используется реже.

TestBean target = new TestBean();
DebugInterceptor di = new DebugInterceptor();
MyInterceptor mi = new MyInterceptor();
ProxyFactory factory = new ProxyFactory(target);
factory.addInterceptor(0, di);
factory.addInterceptor(1, mi);

ITestBean tb = (ITestBean) factory.getProxy();

Мы считаем, что в общем случае лучше выносить связывание приложений из Java-кода, и АОП – не исключение.

Web-фреймворк MVC

В Spring входит мощный и хорошо конфигурируемый Web-фреймворк MVC.

Модель MVC в Spring больше всего похожа на используемую в Struts. Spring Controller похож на Struts Action в том, что это многопоточный сервисный объект, один экземпляр которого работает для всех клиентов. Однако мы считаем, что у Spring MVC есть существенные преимущества перед Struts. Например:

Как и в Struts 1.1, в MVC-приложении Spring можно иметь столько сервлетов-диспетчеров, сколько нужно.

Следующий пример показывает, как простой Spring Controller может обращаться к бизнес-объектам, определенным в том же контексте приложения. Метод handleRequest() этого контроллера выполняет поиск в Google:

public class GoogleSearchController
    implements Controller 
{

  private IGoogleSearchPort google;

  private String googleKey;

  public void setGoogle(IGoogleSearchPort google) 
  {
    this.google = google;
  }

  public void setGoogleKey(String googleKey) 
  {
    this.googleKey = googleKey;
  }

  public ModelAndView handleRequest(
        HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException 
  {
    String query = request.getParameter("query");
    GoogleSearchResult result =
      // Определение свойства Google property пропущено...

    // Использование бизнес-объекта, взаимодействующего с Google
    google.doGoogleSearch(this.googleKey, query,
      start, maxResults, filter, restrict,
      safeSearch, lr, ie, oe);

    return new ModelAndView("googleResults", "result", result);
  }
}

В прототипе, из которого взят этот код, IGoogleSearchPort – это прокси Web-сервисов GLUE, возвращенное Spring FactoryBean. Однако Spring IoC изолирует этот контроллер от нижележащей библиотеки Web-сервисов. Интерфейс может быть реализован как простой Java-объект, тестовая заглушка или EJB-прокси (как будет сказано ниже). Этот контроллер не содержит поиска ресурсов – ничего, кроме кода, нужного для поддержки его Web-взаимодействия.

Spring также предоставляет поддержку привязки данных, форм, визардов и т.д.

Реализация EJB

Если вы решили использовать EJB, Spring может дать серьезный выигрыш как в реализации EJB, так и в клиентском доступе к ним.

Сейчас широко распространена практика рефакторинга объектов бизнес-логики, превращающего их в POJO для использования за EJB-фасадом (кроме всего прочего, это существенно облегчает юнит-тестирование бизнес-логики, так как EJB сильно зависят от контейнера, и их трудно тестировать изолированно). Spring предоставляет удобные суперклассы для сессионных и управляемых событиями bean-ов, что существенно упрощает рефакторинг с помощью автоматической загрузки BeanFactory, основанной на XML-документе, входящем в EJB Jar-файл.

Это значит, что stateless session EJB может получить и использовать POJO-объект так:

import org.springframework.ejb.support.AbstractStatelessSessionBean;

public class MyEJB extends AbstractStatelessSessionBean
  implements MyBusinessInterface 
{
  private MyPOJO myPOJO;

  protected void onEjbCreate() 
  {
    this.myPOJO = getBeanFactory().getBean("myPOJO");
  }

  public void myBusinessMethod()
  {
    this.myPOJO.invokeMethod();
  }
}

Предполагая, что MyPOJO – это интерфейс, то реализующий его класс, а также любая конфигурация, нужная ему, от примитивных свойств до других работающих с ним объектов, спрятаны в XML-определении BeanFactory.

Мы сообщаем Spring, откуда загружать XML-документ, в переменной среды ejb/BeanFactoryPath, определение которой находится в стандартном дескрипторе развертывания ejb-jar.xml:

<session>
  <ejb-name>myComponent</ejb-name>
  <local-home>com.test.ejb.myEjbBeanLocalHome</local-home>
  <local>com.mycom.MyComponentLocal</local>
  <ejb-class>com.mycom.MyComponentEJB</ejb-class>
  <session-type>Stateless</session-type>
  <transaction-type>Container</transaction-type>

  <env-entry>
    <env-entry-name>ejb/BeanFactoryPath</env-entry-name>
    <env-entry-type>java.lang.String</env-entry-type>
    <env-entry-value>/myComponent-ejb-beans.xml</env-entry-value>
  </env-entry>
</session>

Файл myComponent-ejb-beans.xml будет загружен из места, указанного в classpath: в данном случае это корень EJB Jar-файла. Каждый EJB может указать свой XML-документ, так что этот механизм можно применять несколько раз к одному EJB Jar-файлу.

Суперклассы Spring реализуют такие методы жизненного цикла EJB, как setSessionContext() и ejbCreate(), предоставляя разработчику необязательную возможность реализовать метод onEjbCreate().

Использование EJB

Spring упрощает не только создание, но и использование EJB. Многие EJB-приложения используют паттерны Service Locator и Business Delegate. Это лучше, чем распыление JNDI lookup по всему клиентскому коду, но у обычных реализаций этих паттернов есть ряд недостатков.

По этим и другим причинам традиционное применение EJB, как и продемонстрировали такие приложения, как Sun Adventure Builder или OTN J2EE Virtual Shopping Mall, могут снизить продуктивность и создают дополнительную сложность.

Spring представляет "бизнес-делегаты без кодирования" (codeless business delegates). При использовании Spring вам никогда не придется писать еще один Service Locator или дублировать методы, вручную кодируя Business Delegate, если вы не добавляете чего-нибудь действительно нужного.

Представьте, например, что у нас есть Web-контроллер, использующий локальный EJB. Последуем доброму примеру и используем паттерн EJB Business Methods Interface, чтобы локальный интерфейс EJB расширял не специфичный для EJB интерфейс бизнес методов (одна из главных причин для этого – нужно убедиться, что синхронизация между сигнатурами методов в локальных интерфейсах и классах реализаций bean-ов автоматическая). Назовем этот интерфейс бизнес-методов MyComponent. Конечно, также потребуется реализовать local home-интерфейс и предоставить класс реализации bean-а, реализующий SessionBean и интерфейс бизнес-методов MyComponent.

При работе с EJB через Spring, единственное, что нужно для подключения контроллера Web-слоя к реализации EJB – предоставить setter-метод типа MyComponent нашему контроллеру.

private MyComponent myComponent;

public void setMyComponent(MyComponent myComponent) 
{
  this.myComponent = myComponent;
}

Впоследствии можно использовать этот экземпляр в любом бизнес-методе.

Все остальное Spring сделает автоматически, на основе XML-определения bean-а. LocalStatelessSessionProxyFactoryBean – это обобщенная фабрика, которую можно использовать для любого EJB.

<bean id="myComponent"
  class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">

  <property name="jndiName">
    <value>myComponent</value>
  </property>
  <property name="businessInterface">
    <value>com.mycom.MyComponent</value>
  </property>
</bean>

<bean id="myController"
      class = "com.mycom.myController">
  <property name="myComponent">
    <ref bean="myComponent"/>
  </property>
</bean>

За сценой силами Spring АОП-фреймворка творится немало магии, хотя для достижения результата не обязательно использовать концепции АОП. Bean-определение "myComponent" создает для EJB прокси, реализующее интерфейс бизнес-метода. EJB local home кэшируется при запуске, поэтому производится только один JNDI lookup. При каждом вызове EJB прокси вызывает метод create() локального EJB и соответствующий бизнес-метод EJB.

Bean-определение myController делает это прокси значением свойства myComponent класса контроллера.

Механизм обращения к EJB значительно упрощает код приложения:

Тесты производительности и опыт применения в реальных приложениях показывают, что снижение производительности при таком подходе минимально, и, как правило, практически незаметно.

Такой же подход применим к удаленным EJB, через org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean. Однако спрятать RemoteExceptions интерфейса бизнес-методов удаленного EJB не выйдет.

Тестирование

Как вы, вероятно, поняли, я и другие разработчики Spring убеждены в важности юнит-тестов. Мы считаем, что очень важно как тестирование самого фреймворка, так и то, что одной из главных целей дизайна фреймворка должно быть упрощение юнит-тестирования приложений, созданных с его использованием.

Spring включает прекрасные средства тестирования. Мы считаем, что Spring-приложения легко тестировать, так как:

Возможность размещения BeanFactory вне контейнера дает интересную возможность в процессе разработки. В нескольких проектах Web-приложений, ведущихся с использованием Spring, работа начиналась с определения бизнес-интерфейсов и тестирования их реализаций вне Web-контейнера. И только когда бизнес-функциональность была почти готова, добавлялся тонкий слой, обеспечивающий Web-интерфейс.

Кто использует Spring?

Несмотря на то, что Spring – относительно новый проект, у нас есть впечатляющее и растущее сообщество пользователей. Среди них крупные инвестиционные банки, известные доткомы, несколько консультационных контор, академические институты и так далее.

Многие пользователи используют Spring целиком или отдельные его части. Например, многие начинают с использования нашего JDBC и другой функциональности доступа к данным.

Итоги

Spring – это мощный Framework, решающий многие распространенные проблемы J2EE.

Spring предоставляет способ управления бизнес-объектами и поощряет хорошее программирование, например, использование интерфейсов вместо классов. Архитектурная основа Spring – инверсия управления (Inversion of Control), основанная на использовании свойств JavaBean.Но это только часть целого. Spring уникален в том, что он использует IoC-контейнер как основной строительный блок как решение для всех архитектурных уровней.

Spring предоставляет уникальную абстракцию доступа к данным, включая простой и продуктивный JDBC-Framework, значительно повышающий продуктивность и снижающий вероятность ошибок. Архитектура доступа к данным в Spring интегрируется с Hibernate и другими ORM-решениями.

Spring предоставляет уникальную абстракцию управления транзакциями с непротиворечивой программной моделью, строящейся поверх разнообразных транзакционных технологий – JTA, JDBC и так далее.

В Spring есть также АОП-Framework, написанный на стандартной Java, и обеспечивающий доступность для POJO декларативного управления транзакциями и других корпоративных служб – или, по желанию, реализацию собственного аспекта. Этот фреймворк достаточно мощен, чтобы позволить многим приложениям обойтись без сложностей EJB, и в то же время использовать ряд ключевых служб, традиционно ассоциируемых с EJB.

Spring также содержит мощный и гибкий MVC Web-фреймворк, интегрируемый с IoC-контейнером.

Дополнительная информация


Copyright © 1994-2016 ООО "К-Пресс"