- PVSM.RU - https://www.pvsm.ru -
Дано web приложение на Java и Flex. Для связи используется Blaze DS или подобная технология, использующая AMF сериализацию. На стороне сервера и на стороне клиента явно или неявно присутствуют DTO (data transfer objects) и интерфейсы remote сервисов. В подобных приложениях стоит проблема синхронизации кода DTO между клиентом и сервером. Конечно, если приложение полностью покрыто тестами, рассинхронизация между Java и ActionScript исходниками выявится во время тестирования, но есть возможность получить feedback еще раньше – уже во время компиляции.
Для этого нужно генерировать DTO и remote интерфейсы каждый раз при сборке. В случае изменения интерфейсов DTO или remote интерфейсов на сервере они соответствующим образом изменятся и на клиенте. Это позволит узнать о проблемах уже на этапе компиляции, не дожидаясь тестов, кроме того, нельзя исключать вероятности, что тесты не полностью покрывают исходный код.
Для генерации AS DTO есть множество библиотек, например clear toolkit [1], pimento [2] или Gas3 [3] из Granite DS.
Библиотека Gas3 интегрирована в flexmojos [4], она позволяет редактировать шаблоны для генерации классов и выглядит более предпочтительно. Далее в примерах используется именно эта библиотека.
Для примера генерации DTO возьмем java класс SampleDto
.
public class SampleDto {
public String field1;
public String field2;
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
}
Разместим этот класс в maven модуле git.example.com:jar:jar
.
Генерировать ActionScript код будем в модуль git.example.com:flex:swf
. Для того чтобы сгенерировать AS код из SampleDto.java
нужно в настройке flexmojos добавить следующее:
<plugin>
<groupId>org.sonatype.flexmojos</groupId>
<artifactId>flexmojos-maven-plugin</artifactId>
<version>3.9</version>
<extensions>true</extensions>
<configuration>
<includeJavaClasses>
<includeJavaClass>com.example.*</includeJavaClass>
</includeJavaClasses>
</configuration>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
Все генерируемые классы состоят из двух частей: самого класса и предка (с постфиксом Base). В нашем случае после генерации мы получим классы SampleDto
и SampleDtoBase
. Основной класс генерируется в случае, если класса с таким именем не существует. Предок генерируется всегда и перезаписывает старый файл, если он есть. Всю дополнительную функциональность нужно добавлять только в основной класс, если он есть, в этом случае генератор его не тронет. Если добавить новое поведение в Base класс, тогда генератор все удалит.
[Bindable]
[RemoteClass(alias="com.example.SampleDto")]
public class SampleDto extends SampleDtoBase {
}
[Bindable]
public class SampleDtoBase implements IExternalizable {
public var _field1:String;
public var _field2:String;
public function set field1(value:String):void {
_field1 = value;
}
public function get field1():String {
return _field1;
}
public function set field2(value:String):void {
_field2 = value;
}
public function get field2():String {
return _field2;
}
public function readExternal(input:IDataInput):void {
_field1 = input.readObject() as String;
_field2 = input.readObject() as String;
}
public function writeExternal(output:IDataOutput):void {
output.writeObject(_field1);
output.writeObject(_field2);
}
}
Теперь сгенерируем remote интерфейс. Создадим модуль git.example.com:jar:jar
java класс SampleService
и проаннотируем его.
@RemoteDestination(id = "sampleService", channel = "amf")
public class SampleService {
public final String sampleMethod1() {
return "test output";
}
public final String getSampleField1(@Param("sampleDto") final SampleDto sampleDto) {
return sampleDto.getField1();
}
public final String getSampleField2(@Param("sampleDto") final SampleDto sampleDto) {
return sampleDto.getField2();
}
@IgnoredMethod
public final void ignoredMethod() {
}
}
Аннотация @RemoteDestination
описывает канал и имя end point’а для сервиса и вешается на класс. @Param
задает имя параметра в сгенерированном методе, но она не обязательна. @IgnoredMethod
говорит генератору игнорировать помеченный метод.
Для генерации используем те же настройки, что и для DTO. На выходе получаем два класса:
[RemoteClass(alias="com.example.SampleService")]
public class SampleService extends SampleServiceBase {
}
public class SampleServiceBase extends RemoteObject {
private var _initRemote:Boolean = false;
private function initRemote():void {
destination = "sampleService";
channelSet = new ChannelSet();
channelSet.addChannel(ServerConfig.getChannel("amf"));
_initRemote = true;
}
public function sampleMethod1():void {
if (!_initRemote)
initRemote();
getOperation("sampleMethod1").send();
}
public function getSampleField2(sampleDto:SampleDto):void {
if (!_initRemote)
initRemote();
getOperation("getSampleField2").send(sampleDto);
}
public function getSampleField1(sampleDto:SampleDto):void {
if (!_initRemote)
initRemote();
getOperation("getSampleField1").send(sampleDto);
}
public function addOperationListener(op:Function, type:String, handler:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
if (op == this.sampleMethod1)
this.getOperation("sampleMethod1").addEventListener(type, handler, useCapture, priority, useWeakReference);
if (op == this.getSampleField2)
this.getOperation("getSampleField2").addEventListener(type, handler, useCapture, priority, useWeakReference);
if (op == this.getSampleField1)
this.getOperation("getSampleField1").addEventListener(type, handler, useCapture, priority, useWeakReference);
}
public function removeOperationListener(op:Function, event:String, handler:Function):void {
if (op == this.sampleMethod1)
this.getOperation("sampleMethod1").removeEventListener(event, handler);
if (op == this.getSampleField2)
this.getOperation("getSampleField2").removeEventListener(event, handler);
if (op == this.getSampleField1)
this.getOperation("getSampleField1").removeEventListener(event, handler);
}
}
Gas3 позволяет модифицировать шаблоны для генерации. Изменим шаблон для генерации remote интерфейсов, чтобы иметь возможность вешать Responder’ы на методы. Шаблоны пишутся на groovy и достаточно объемные, поэтому они не представлены в статье, желающие могут посмотреть шаблоны в репозитории для этого примера тут [5]. Для модификации шаблонов нужно добавить в конфигурацию flexmojos секцию templates:
<templates>
<base-remote-template>
${project.basedir}/src/main/generator-templates/remoteBase.gsp
</base-remote-template>
</templates>
В результате получаем такой код сервиса:
public class SampleServiceBase extends RemoteObject {
private static const logger:ILogger = Log.getLogger(getQualifiedClassName(SampleService).replace("::", "."));
private var _initRemote:Boolean = false;
public function SampleServiceBase() {
super();
}
private function initRemote():void {
destination = "sampleService";
channelSet = new ChannelSet();
channelSet.addChannel(ServerConfig.getChannel("amf"));
_initRemote = true;
}
public function getSampleField1(sampleDto:SampleDto, responder:IResponder = null):AsyncToken {
if (!_initRemote) {
initRemote();
}
var asyncToken:AsyncToken = getOperation("getSampleField1").send(sampleDto);
if (responder) {
asyncToken.addResponder(responder);
}
if (Log.isDebug()) {
logger.debug("Method <getSampleField1> invoked with parameters <{0}>", sampleDto);
}
return asyncToken;
}
public function sampleMethod1(responder:IResponder = null):AsyncToken {
if (!_initRemote) {
initRemote();
}
var asyncToken:AsyncToken = getOperation("sampleMethod1").send();
if (responder) {
asyncToken.addResponder(responder);
}
if (Log.isDebug()) {
logger.debug("Method <sampleMethod1> invoked with parameters ");
}
return asyncToken;
}
public function getSampleField2(sampleDto:SampleDto, responder:IResponder = null):AsyncToken {
if (!_initRemote) {
initRemote();
}
var asyncToken:AsyncToken = getOperation("getSampleField2").send(sampleDto);
if (responder) {
asyncToken.addResponder(responder);
}
if (Log.isDebug()) {
logger.debug("Method <getSampleField2> invoked with parameters <{0}>", sampleDto);
}
return asyncToken;
}
public function addOperationListener(op:Function, type:String, handler:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
if (op == this.getSampleField1) {
this.getOperation("getSampleField1").addEventListener(type, handler, useCapture, priority, useWeakReference);
}
if (op == this.sampleMethod1) {
this.getOperation("sampleMethod1").addEventListener(type, handler, useCapture, priority, useWeakReference);
}
if (op == this.getSampleField2) {
this.getOperation("getSampleField2").addEventListener(type, handler, useCapture, priority, useWeakReference);
}
}
public function removeOperationListener(op:Function, event:String, handler:Function):void {
if (op == this.getSampleField1) {
this.getOperation("getSampleField1").removeEventListener(event, handler);
}
if (op == this.sampleMethod1) {
this.getOperation("sampleMethod1").removeEventListener(event, handler);
}
if (op == this.getSampleField2) {
this.getOperation("getSampleField2").removeEventListener(event, handler);
}
}
}
Кроме remote интерфейсов Gas3 дает возможность менять шаблоны для java интерфейсов, для обычных bean’ов и для JPA entity bean’ов.
Как результат:
Для удобства мною создано web-приложение, работающее с этими сущностями. Исходный код можно посмотреть тут [6].
p.s. Я в курсе нехороших тенденций в области flash/flex, но может быть кому-то будет полезен мой опыт.
Автор: dmmm
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/8565
Ссылки в тексте:
[1] clear toolkit: http://sourceforge.net/projects/cleartoolkit/
[2] pimento: http://www.spicefactory.org/
[3] Gas3: http://www.graniteds.org/public/docs/2.3.0/docs/reference/en-US/html/graniteds.validation.html#validation.gas3
[4] flexmojos: http://flexmojos.sonatype.org/
[5] тут: https://github.com/dmalch/sample-generate-flex-dto-and-services/blob/master/flex/src/main/generator-templates/remoteBase.gsp
[6] тут: https://github.com/dmalch/sample-generate-flex-dto-and-services
[7] http://www.graniteds.org/confluence/display/DOC/3.+Gas3+Code+Generator: http://www.graniteds.org/confluence/display/DOC/3.+Gas3+Code+Generator
Нажмите здесь для печати.