Search
Write a publication
Pull to refresh

Знакомьтесь, JBoss Seam

В комментариях к статье высказывалась мысль, что в Java-мире Spring Framework с его Inversion of Control является стандартом de facto. Позвольте представить вам framework, который незаслуженно обделён вниманием в русскоязычном интернете и, тем не менее, претендует на звание стандарта de jure.


Как Вы уже догадались, речь пойдёт о JBoss Seam, и в данной заметке я постараюсь дать ему краткую характеристику.




Введение


JBoss Seam – это каркас для приложений на основе Java EE, разрабатываемый командой JBoss и распространяемый по лицензии LGPL. Руководитель проекта – Pete Muir. Идейный двигатель проекта – Gavin King, известный Java-сообществу как создатель популярного ORM решения Hibernate. В данный момент под руководством Гэвина идет работа над спецификацией Web Beans (JSR-299), которая уже находится на стадии Final Draft и выложена на публичное обозрение. Этот факт примечателен тем, что JSR-299 включен в платформу Java EE 6 в качестве унифицированной модели компонентов для Enterprise Java. И начиная с версии Seam 3.0, ядро фреймворка будет фактически являться имплементацией Web Beans. Кстати, насколько мне известно, руководство SpringSource проигнорировало предложение присоединиться к экспертной группе JSR-299 и принять участие в разработке спецификации (разработка ведется при участии представителей JBoss, Sun, Oracle, а также Google, в качестве создателя Guice). Помимо этого, Кинг приложил руку и к созданию спецификаций JSF 2.0, JPA 1.1 и EJB 3.1. Тем интереснее выглядит детище Кинга в глазах приверженцев стандартов. Так в чем же преимущества Seam перед другими фреймворками?



Особенности


Как сказал Dan Allen, разработчик Seam 3.0: «В мире, переполненном фреймворками, Seam – это unframework». Seam не заключает нас в рамки использования какой-либо новой модели программирования. Вместо этого, он обеспечивает простую интеграцию стандартов Java EE, в первую очередь EJB3, JSF, JPA и JAAS (Java Authentication and Authorization Service), и делает их более доступными, функциональными и приятными в использовании. Seam – не просто очередной фреймворк для генерации HTML-страниц. Это полноценный каркас для веб-приложений. Seam рассматривает такие проблемы, как сохранение состояния объектов, конкурентные запросы, асинхронные события, управление состоянием, безопасность, генерация PDF-файлов и графиков, workflows, wikitext, веб-сервисы, кэширование и многое другое.


Seam объединяет JPA и Hibernate3 для persistence-уровня, EJB Timer Service и Quartz для обработки асинхронных событий, jBPM для описания workflow, JBoss Rules для бизнес-правил, Meldware Mail для отправки электронной почты, JMS для сообщений, Hibernate Search и Lucene для полнотекстового поиска, JBoss Cache для кэширования фрагментов страницы. Seam использует инновационный слой безопасности, управляемой правилами на основе JBoss Rules. Даже есть библиотеки JSF тегов для PDF, диаграмм, отправки почты и wiki-текста. Компоненты Seam можно вызывать посредством обращения через веб-сервисы, асинхронно из JavaScript или Google Web Toolkit, ну и, конечно же, напрямую из JSF. О каждом из этих аспектов использования Seam можно написать целую статью. А пока затронем лишь основные моменты.



Унифицированная модель компонентов

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


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



Интеграция JSF с EJB 3

С помощью Seam вполне возможно написать приложение, где каждый компонент – EJB. В принципе неудивительно, что после EJB 2.x большинство Java-разработчиков считают EJB-компоненты некими «тяжеловесными» объектами. Тем не менее, версия EJB 3.0 полностью изменила подход к созданию EJB. Теперь это не более чем аннотированный Java Bean. И Seam даже одобряет использование session-бинов в качестве слушателей JSF действий.


С другой стороны, если в данный момент Вы предпочитаете не использовать EJB, то Вам и не обязательно этого делать. Фактически, любой Java класс может быть компонентом Seam. И Seam предоставляет все функциональные возможности, которые ожидаются от «легковесных» контейнеров для любых компонентов, будь то EJB или обычный Java Bean.



AJAX

Seam поддерживает два, основанных на JSF, AJAX-фреймворка: JBoss RichFaces и ICEfaces. Оба этих решения позволяют ощутить всю мощь AJAX'а без написания какого-либо JavaScript кода. В качестве альтернативы Seam предлагает собственный JavaScript remoting, позволяющий вызывать компоненты асинхронно из JavaScript без необходимости создания промежуточного слоя.



Пример приложения


Вероятно, разговор о стандартах платформы Java EE и о роли Seam, в качестве связующего звена этих самых стандартов, создает впечатление некой «монструозности». Но это далеко не соответствует истине. С помощью Seam можно достаточно быстро получить работающий прототип приложения, затратив минимум усилий. Я специально рассматриваю в качестве небольшого тестового приложения упрощенный контакт-лист из Seam examples. Минимум кода (а Java-код для CRUD-операций вообще отсутствует), но прочувствовать простоту использования фреймворка в контексте JPA+JSF уже можно.


Итак, опишем простую сущность Contact с полями name, address и phone


package org.jboss.seam.example.contactlist;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version;

import org.hibernate.validator.Length;

@Entity
public class Contact
{
  @Id @GeneratedValue
  private Long id;
 
  @Length(max=50)
  private String name;
  @Length(max=250)
  private String address;
  @Length(max=20)
  private String phone;
 
  @Version
  private int version;
 
  /* геттеры и сеттеры опущены, но они есть! */
}

* This source code was highlighted with Source Code Highlighter.

Для тех, кто знаком с Java Persistence API, здесь ничего нового. Также для реализации CRUD операций нам потребуется задействовать класс org.jboss.seam.framework.EntityHome (не путать с интерфейсом EJBHome), предоставляющий спектр базовых операций по управлению состоянием экземпляра Entity конкретного типа. В нашем примере достаточно описать его в components.xml – базовый дескриптор для Seam-приложений, располагается в директории WEB-INF. В более сложных случаях возможно потребуется создать подкласс на основе EntityHome или же написать свой компонент.


<?xml version="1.0" encoding="UTF-8"?><br><components xmlns="http://jboss.com/products/seam/components"<br>     xmlns:core="http://jboss.com/products/seam/core"<br>     xmlns:fwk="http://jboss.com/products/seam/framework"<br>     xmlns:persistence="http://jboss.com/products/seam/persistence"<br>     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br>     xsi:schemaLocation=<br>        "http://jboss.com/products/seam/core jboss.com/products/seam/core-2.1.xsd<br>         jboss.com/products/seam/framework jboss.com/products/seam/framework-2.1.xsd<br>         jboss.com/products/seam/persistence jboss.com/products/seam/persistence-2.1.xsd<br>         jboss.com/products/seam/components jboss.com/products/seam/components-2.1.xsd"><br> <persistence:managed-persistence-context name="entityManager"<br>     persistence-unit-jndi-name="java:/contactlistEntityManagerFactory"/> <br><br> <core:init jndi-pattern="jboss-seam-contactlist/#{ejbName}/local"/> <br><br> <factory name="contact" value="#{contactHome.instance}"/><br><br> <fwk:entity-home name="contactHome"<br>      entity-class="org.jboss.seam.example.contactlist.Contact"/><br><br> <fwk:entity-query name="contacts"<br>        max-results="5"><br>    <fwk:ejbql>from Contact</fwk:ejbql><br>    <fwk:order>name</fwk:order><br>    <fwk:restrictions><br>     <value>lower(name) like lower( concat( #{exampleContact.name}, '%' ) )</value><br>    </fwk:restrictions><br> </fwk:entity-query><br> <br> <component name="exampleContact"<br>      class="org.jboss.seam.example.contactlist.Contact"/><br></components><br><br>* This source code was highlighted with Source Code Highlighter.

Здесь entityManager – компонент, реализующий интерфейс javax.persistence.EntityManager. Jndi-имя фабрики задается в обычном persistence.xml. Тег factory реализует паттерн «Factory Method». В данном случае при обращении к компоненту по имени «contact» будет вызван метод getInstance() у компонента contactHome. Этот метод возвращает нам чистый объект, который можно будет отредактировать и записать в базу данных, или же достанет из БД существующий объект, если перед вызовом getInstance() мы вызвали setId() у EntityHome-компонента с параметром, равным идентификатору нужной нам сущности. Кстати, можно обойтись и без явного вызова setId(), но об этом позднее. С описанием contactHome вопросов не возникает. А вот fwk:entity-query вызывает куда больший интерес. Этот тег делает прозрачным для разработчика создание и работу с javax.persistence.Query, а также поддерживает пагинацию результатов запроса без дополнительного кода. Ну и дополнительный компонент exampleContact, который потребуется для организации поиска контактов. Да и вообще, инкапсуляция параметров запроса в подходящий объект – хороший тон. Как видно из описания компонента contacts, параметры для поиска контактов мы достаём именно из него.


Еще нам потребуются 2 facelet'а. Первый из них – search.xhtml:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><br><html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets"<br>         xmlns:h="http://java.sun.com/jsf/html"<br>         xmlns:f="http://java.sun.com/jsf/core"<br>         xmlns:s="http://jboss.com/products/seam/taglib"><br> <head><br> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /><br> <title>Contact List</title><br> <link href="screen.css" rel="stylesheet" type="text/css" /><br> </head><br> <body> <br> <div class="menuButtons"><br>  <s:link view="/search.xhtml" value="Search Contacts"><br>     <f:param name="name" /><br>     <f:param name="firstResult" /><br>  </s:link><br>  <s:link view="/editContact.xhtml" value="Create New Contact"><br>    <f:param name="contactId"/> <!-- suppress propagation of contact id page parameter --><br>  </s:link><br> </div><br> <div class="body">    <br>  <h1>ContactList</h1>  <br>  <h:messages styleClass="message"/><br>  <!-- search box --><br>  <div class="dialog"><br>    <h:form><br>     <span class="prop"><br>      <span class="name">Name:</span><br>      <span class="value"><h:inputText value="#{exampleContact.name}"/></span><br>     </span><br>     <h:commandButton value="Search" action="newsearch"/><br>    </h:form><br>  </div>  <br>  <!-- search results --><br>  <table><br>    <tr><br>     <th>Name</th><br>     <th>Phone</th><br>     <th>Address</th><br>    </tr><br>    <ui:repeat value="#{contacts.resultList}" var="cont"><br>     <tr><br>     <td><br>      <s:link view="/editContact.xhtml" value="#{cont.name}"><br>        <f:param name="contactId" value="#{cont.id}"/><br>      </s:link><br>     </td><br>     <td>#{cont.phone}</td><br>     <td>#{cont.address}</td><br>     </tr><br>    </ui:repeat><br>  </table><br>  <h:outputText value="No contacts to display" rendered="#{empty contacts.resultList}" styleClass="message"/>  <br>  <!-- незамысловатая пагинация --><br>  <div class="tableControl">  <br>    <s:link view="/search.xhtml" rendered="#{contacts.previousExists}" value="<< First Page"><br>     <f:param name="firstResult" value="0"/><br>    </s:link>    <br>    <s:link view="/search.xhtml" rendered="#{contacts.previousExists}" value="< Previous Page"><br>     <f:param name="firstResult" value="#{contacts.previousFirstResult}"/><br>    </s:link>    <br>    <s:link view="/search.xhtml" rendered="#{contacts.nextExists}" value="Next Page >"><br>     <f:param name="firstResult" value="#{contacts.nextFirstResult}"/><br>    </s:link>    <br>    <s:link view="/search.xhtml" rendered="#{contacts.nextExists}" value="Last Page >>"><br>     <f:param name="firstResult" value="#{contacts.lastFirstResult}"/><br>    </s:link>    <br>  </div>     <br>  </div>  <br> </body></html><br><br>* This source code was highlighted with Source Code Highlighter.

Обращаю ваше внимание на тот факт, что тег s:link генерирует html-элемент «a», со всеми вытекающими последствиями. Да-да, Seam позволяет использовать GET для JSF запросов! И в ближайшем будущем (JSF 2.0) этот функционал будет определен в спецификации. А в остальном, мы видим обращения посредством expression language к компонентам, описанным в components.xml. Правда и в EL Seam внёс свои коррективы. Например, в приведенном выше коде мы видим обращение к cont.phone, cont.address вне JSF тегов, что позволяет обойтись без лишних элементов типа h:outputText. На самом деле интерполяция строковых выражений (а равно и локализация приложений) в Seam реализована на очень высоком уровне. К примеру, если Вы используете Hibernate Validator, Вы можете указать в качестве message-атрибута аннотации NotNull (и не только этой) строку вида "#{messages.validate.null_field}". Помимо этого, JBoss EL при использовании JSF вкупе с Facelets позволяет использовать параметры для методов и создавать совершенно логичные с точки зрения разработчика конструкции, которые невозможны в JSP:


<h:dataTable value="#{items}" var="item"><br> <h:сolumn><br> <h:commandLink value="Select #{item.name}" action="#{itemSelector.select(item})" /><br> </h:column><br></h:dataTable><br><br>* This source code was highlighted with Source Code Highlighter.

Второй facelet – editContact.xhtml:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><br><html xmlns="http://www.w3.org/1999/xhtml"<br>        xmlns:ui="http://java.sun.com/jsf/facelets"<br>         xmlns:h="http://java.sun.com/jsf/html"<br>         xmlns:f="http://java.sun.com/jsf/core"<br>         xmlns:s="http://jboss.com/products/seam/taglib"<br>        template="template.xhtml"><br><head><br> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /><br> <title>Edit Contact</title><br> <link href="screen.css" rel="stylesheet" type="text/css" /><br></head><br><body><br> <div class="menuButtons"><br> <s:link view="/search.xhtml" value="Search Contacts"/><br> <s:link view="/editContact.xhtml" value="Create New Contact"><br>    <f:param name="contactId"/> <!-- suppress propagation of contact id page parameter --><br> </s:link><br> <s:link view="/editContact.xhtml" value="Edit Contact" rendered="#{contactHome.managed}"/><br> </div><br> <div class="body"><br> <h1>Edit Contact</h1><br> <h:form><br>    <h:messages globalOnly="true" styleClass="message"/><br>     <!-- editable fields --><br>    <s:validateAll><br>     <f:facet name="aroundInvalidField"><br>     <s:span styleClass="errors"/><br>     </f:facet><br>     <f:facet name="afterInvalidField"><br>     <s:span> <s:message/></s:span><br>     </f:facet><br>     <div class="dialog"><br>     <table><br>     <tr class="prop"><br>        <td class="name">Name:</td><br>        <td class="value"><br>         <s:decorate><h:inputText id="name" value="#{contact.name}" required="true"/></s:decorate><br>        </td><br>     </tr><br>     <tr class="prop"><br>        <td class="name">Phone:</td><br>        <td class="value"><br>         <s:decorate><h:inputText id="phone" value="#{contact.phone}"/></s:decorate><br>        </td><br>     </tr><br>     <tr class="prop"><br>        <td class="name">Address:</td><br>        <td class="value"><br>         <s:decorate><h:inputText id="address" value="#{contact.address}"/></s:decorate><br>        </td><br>     </tr><br>     </table><br>     </div><br>    </s:validateAll><br>    <!-- actions --><br>    <div class="actionButtons"><br>     <h:commandLink action="#{contactHome.update}" value="Update Contact" <br>                    rendered="#{contactHome.managed}"/><br>     <s:link action="#{contactHome.remove}" value="Delete Contact" <br>             rendered="#{contactHome.managed}"/><br>     <h:commandLink action="#{contactHome.persist}" value="Create Contact" <br>                    rendered="#{!contactHome.managed}"/><br>     <s:link view="/search.xhtml" value="Cancel"/><br>    </div><br> </h:form><br> </div><br></body></html><br><br>* This source code was highlighted with Source Code Highlighter.

Здесь мы видим пару новых тегов: s:validateAll и s:decorate. Первый из них задействует механизм проверки правильности ввода данных, второй выводит ошибки для поля, если введенное значение некорректно. Но основной функционал здесь возлагается на компонент contactHome. Как мы видим contactHome предоставляет разработчику стандартный набор CRUD операций (persist, update, remove), а также флаг managed для определения принадлежности объекта persistence-контексту (иными словами, существует ли он в БД, или мы создаём новый). Но всё-таки, как contactHome определяет какой именно объект нам нужен? Ведь мы обошлись без разбора GET-параметров, и не вызывали явно метод contactHome.setId(). Или как выполняется навигация после успешного выполнения операции contactHome.remove? Чтобы всё встало на свои места, рассмотрим еще один конфигурационный файл pages.xml, находится рядом с components.xml в директории WEB-INF.


<?xml version="1.0" encoding="UTF-8"?><pages xmlns="http://jboss.com/products/seam/pages"<br>    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br>    xsi:schemaLocation="http://jboss.com/products/seam/pages jboss.com/products/seam/pages-2.1.xsd"><br> <page view-id="/editContact.xhtml"><br>  <!-- берем request-параметр contactId и записываем его в contactHome.id<br>     предварительно обрабатываем стандартным JSF-конвертером --><br>  <param name="contactId"<br>      value="#{contactHome.id}"<br>      converterId="javax.faces.Long"/><br>  <!-- JSF-like навигация<br>        методы update/persist/remove класса EntityHome возвращают соответствующие строки --><br>  <navigation><br>     <rule if-outcome="updated"><br>     <redirect view-id="/search.xhtml"/><br>     </rule><br>     <rule if-outcome="persisted"><br>     <redirect view-id="/search.xhtml"/><br>     </rule><br>     <rule if-outcome="removed"><br>     <redirect view-id="/search.xhtml"/><br>     </rule><br>  </navigation><br> </page><br> <page view-id="/search.xhtml"><br>  <param name="name"<br>      value="#{exampleContact.name}"/><br>  <param name="firstResult"<br>      value="#{contacts.firstResult}"/><br>  <navigation><br>     <!-- взгляните на описание кнопки Search в search.xhtml --><br>     <rule if-outcome="newsearch"><br>      <redirect view-id="/search.xhtml"><br>         <param name="firstResult" value="0"/><br>      </redirect><br>     </rule><br>  </navigation><br> </page><br> <exception class="javax.persistence.PersistenceException"><br>  <end-conversation/><br>  <redirect><br>     <!-- message выводится в h:messages --><br>     <message>Database access failed</message><br>  </redirect><br> </exception><br> <exception class="org.jboss.seam.framework.EntityNotFoundException"><br>  <http-error error-code="404"/><br> </exception><br> <exception><br>  <end-conversation/><br>  <redirect view-id="/search.xhtml"><br>     <message>Unexpected failure</message><br>  </redirect><br> </exception></pages><br><br>* This source code was highlighted with Source Code Highlighter.

После просмотра вышеприведенного кода складывается ложное мнение, что Seam – это программирование на XML. На самом деле я выбрал именно этот пример лишь потому, что в подобных статьях, как правило, появляются комментарии вида: «А вот в Ruby...». Базовая реализация CRUD-операций уже встроена в фреймворк, и доступна разработчику через класс EntityHome, а конкретный экземпляр этого класса можно описать всего одной строкой в конфигурационном файле. Но в большинстве случаев Seam всё же предполагает использование Java Bean (EJB или POJO) компонентов с аннотациями. Seam расширяет аннотации EJB 3.0 своим собственным набором для декларативного управления состоянием объекта и работы с контекстами. И одним из основополагающих принципов Seam является минимизация конфигурационных файлов. Это позволяет избежать надоедливых объявлений managed-bean'ов, и уменьшить количество требуемого XML за исключением той информации, которая по определению соответствует структуре XML-документа (навигационные правила в JSF). Иными словами, в приложении, написанном на Seam+JSF, может полностью отсутствовать faces-config.xml.


Вообще в пользу Seam можно перечислить множество вещей. Это и динамическая биекция зависимостей (или просто bijection), радикальн

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.