Статьи

Миграция веб-приложений Java EE в службы Adobe Flex и Granite Data Services

Одной из первоначальных целей проекта Granite Data Services (текущая версия 1.2.0 GA) было облегчить миграцию существующих приложений J2EE на технологию Adobe Flex RIA. Эталонная структура, используемая в наших приложениях, была довольно классической: JSF + EJB + Hibernate.

Очевидно, что простым способом было сохранить его как сервисный уровень (EJB + Hibernate), работающий только над реализацией веб-уровня (JSF). С технологиями Flex-серверов, предоставляемыми Adobe (LCDS и BlazeDS), маловероятно, что вы можете сохранить уровень обслуживания таким, какой он есть: LCDS продвигает собственные концепции обслуживания и постоянства, в то время как BlazeDS не поддерживает одну из наиболее важных функций механизмов JPA: отложенная загрузка.

С GraniteDS вы получаете полную поддержку JPA и можете легко интегрироваться не только с EJB, но также с сервисами Seam, Spring и даже Guice. По сути, большая часть работы заключается в переносе управляемых компонентов JSF (или компонентов Struts) и представлений в клиент Flex, а GraniteDS очень поможет вам с инструментами генерации кода и инфраструктурой клиента Tide.

Например, предположим, что у вас есть простая модель данных, подобная этой:

@Entity
public class Brand {

@Id @GeneratedValue
private int id;

@Basic
private String name;

(1) @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="brand")
private Set<Car> contacts = new HashSet<Car>();

// getters/setters...
}

@Entity
public class Car {

@Id @GeneratedValue
private int id;

@ManyToOne(optional=false)
private Brand brand;

@Basic
private String model;

// getters/setters...
}

(1) Обратите внимание на параметр FetchType.LAZY в коллекции автомобилей.

Миграция вашего веб-слоя во Flex требует наличия в ActionScript3 представления ваших компонентов. С помощью генератора кода Gas3 (доступного как задача Ant или построитель Eclipse) эта модель данных автоматически реплицируется в ActionScript3: запустив генератор, вы получите классы Brand.as и Car.as, реализация которых включает в себя весь необходимый код, необходимый для сериализации данных из ваших Java-сервисов в клиентское приложение Flex и наоборот (и необходимую реализацию поддержки отложенной загрузки).

Вот и все для вашей модели данных, без ограничений по сложности (наследование, сопоставленный суперкласс, встроенные bean-компоненты, объявления внутренних перечислений и т. Д.). Вы просто используете генератор кода, чтобы отразить вашу модель Java в ее эквиваленте ActionScript3.

Предположим теперь, что у вас есть сессионный EJB-компонент с базовой операцией поиска, подобной этой:

@Session
public class CarServiceBean implements CarService {

@PersistenceContext
protected EntityManager manager;

public findAllBrands() {
return manager.createQuery("select b from Brand b").getResultList();
}
}

С Tide для доступа к этому EJB из приложения Flex вам понадобится файл конфигурации Flex services-config.xml, например:

<services-config>
<services>
<service id="granite-service"
class="flex.messaging.services.RemotingService"
messageTypes="flex.messaging.messages.RemotingMessage">
(1) <destination id="ejb">
<channels>
<channel ref="my-graniteamf"/>
</channels>
<properties>
(2) <factory>tideEjbFactory</factory>
(3) <entityManagerFactoryJndiName>java:/DefaultEMF</entityManagerFactoryJndiName>
</properties>
</destination>
</service>
</services>

<factories>
<factory id="tideEjbFactory" class="org.granite.tide.ejb.EjbServiceFactory">
<properties>
<lookup>myApp/{capitalized.component.name}Bean/local</lookup>
</properties>
</factory>
</factories>

<!-- skipped channel definition -->
</services-config>

Важными частями в этой конфигурации являются:

(1) Используется только один пункт назначения, который должен называться «ejb» (этот идентификатор пункта назначения будет автоматически распознан и использован Tide для EJB без дальнейшей настройки).

(2) Место назначения связано с tideEjbFactory, и этой фабрике удастся найти сессионный компонент на основе шаблона String, где {capitalized.component.name} будет заменен во время выполнения на имя службы, которую вы вызываете в своем клиенте код (см. код MXML ниже).

(3) Поскольку Tide должен иметь доступ к EntityManager для прозрачных операций отложенной загрузки, вы также должны предоставить действительное имя JNDI для получения экземпляра EntityManagerFactory.

Наконец, мы можем написать очень простое приложение MXML, подобное этому:

<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns="*"
(1) creationComplete="creationComplete()">

<mx:Script>
<![CDATA[
import mx.collections.ListCollectionView;

import org.granite.tide.ejb.Ejb;
import org.granite.tide.ejb.Context;
import org.granite.tide.events.TideResultEvent;

[Bindable]
private var brands:ListCollectionView;

(2) private var tideContext:Context = Ejb.getInstance().getEjbContext();

(3) function creationComplete():void {
tideContext.carService.findAllBrands(findResult);
}

(4) function findResult(event:TideResultEvent):void {
brands = event.result;
}
]]>
</mx:Script>

<mx:HBox width="100%" height="100%">
(5) <mx:DataGrid id="dgBrands" dataProvider="{brands}"/>

(6) <mx:DataGrid dataProvider="{dgBrands.selectedItem.cars}"/>
</mx:HBox>

</mx:Application>

(1) Когда приложение Flex полностью инициализируется, оно вызывает функцию с именем «creationComplete» (см. (3)).

(2) Перед вышеуказанным вызовом закрытой переменной tideContext назначается одноэлементный контекст, который разрешает вызовы служб в месте назначения «ejb».

(3) Затем выполняется функция creationComplete, которая вызывает метод findAllBrands в нашем вышеупомянутом сеансе EJB CarServiceBean. Дополнительным параметром является имя функции-обработчика, вызываемой, когда результат будет доступен (все вызовы сервера являются асинхронными).

(4)Когда результат доступен, переменной «бренды» присваивается коллекция всех брендов, доступных в базе данных. Эта коллекция содержит экземпляры ранее сгенерированного класса Brand.as, которые реплицируют соответствующие компоненты Java-бинов, загруженные EntityManager.

(5) Первая DataGrid («dgBrands») заполняется объектами Brand, чья коллекция автомобилей неинициализирована (вспомните параметр FetchType.LAZY в классе Brand Java).

(6)Каждый раз, когда вы щелкаете по бренду в первой DataGrid, свойству selectedItem присваивается выбранный бренд. Поскольку доступ к коллекции автомобилей осуществляется с целью заполнения второй DataGrid, эта неинициализированная коллекция прозрачно загружается Tide путем отправки вызова на сервер с выбранным брендом: с помощью EntityManager коллекция унифицированных автомобилей полностью загружается и возвращается в Клиентский контекст Tide. Затем второй DataGrid заполняется всеми автомобилями, зарегистрированными для выбранной марки.

Этот краткий обзор инфраструктуры GraniteDS не дает вам полной картины всех возможностей GraniteDS: служб безопасности для серверов Tomcat, Jetty, GlassFish и (в ближайшее время) WebLogic (с конкретными реализациями для сред Spring и Seam), поддержки Hibernate, TopLink, EclipseLink и (в ближайшее время) движки персистентности OpenJPA, тесная интеграция со структурой Seam (поддержка диалога и биографии), подкачка данных, внедрение и передача в ваши компоненты Tide на стороне Flex (с конкретными аннотациями ActionScript3) и т. Д.

Вы можете увидеть больше о выпуске Granite Data Services 1.2.0 GA на форуме GDS здесь .

Комментарии приветствуются!