Hessian связывает flash и java

February 7, 2008

Введение



Если вы занимаетесь разработкой flash-приложений, которые взаимодействуют с сервером, то ваш выбор вовсе не ограничен xml и amf, как единственными доступными форматами для пересылки данных. Выбор между этими двумя стратегиями (текстовые форматы данных против двоичных) не всегда прост. На одной чаше весов находится небольшой размер файла (xml-файлы бывают ужасно громоздки), также операция сохранения actionscript объекта в xml-документ и обратная ему операция восстановления могут быть слишком медленными и требовать значительных затрат памяти. На второй чаше весов до недавних пор находилась гипотетическая свобода: до конца 2007 г. формат amf был закрыт и не были доступны официальные спецификации формата. Конечно, это не мешало появлению множества библиотек для java, php, ruby … которые умели формировать и читать amf-поток данных (хотя оставался неприятный привкус). А при условии грамотной реализации server-side и четкого отделения слоя занимающегося чтением или записью amf-данных от остальных частей приложения (бизнес логика, работа с СУБД …), может исчезнуть и такой “гипотетически неправильный” фактор, как смена средства для представления данных (ну вы знаете эту сказку про то, что сегодня мы делаем для flash, а завтра для silverlight, а послезавтра …). Ну а историю о том, что amf-основанные приложения трудно отлаживать т.к. не нельзя посмотреть глазками на то, какие данные идут от сервера к клиенту или наоборот, я оставляю в качестве утешения тем, кто ни разу не встречался с charles (http://xk72.com).

Естественно, что вакуум, образовавшийся в сфере “с помощью чего же нам послать данные на сервер”, не мог быть замечен рядом коммерческих (и не очень) организаций и они поспешили предоставить собственные продукты, которые “быстрее, выше и дальше”. Сегодня я расскажу об Hessian – двоичном протоколе для общения между клиентом и сервером. Hessian был разработан компанией caucho (http://hessian.caucho.com/index.xtp). Для тех, кто не знает, это автор одного из наиболее популярных java-серверов resin. Упреждая вопросы, естественно, что caucho не могли сделать ошибки и жестко привязать Hessian только к их серверу. Фактически сам код библиотеки это небольшой архив jar размером чуть менее 300 кб (и никаких внешних зависимостей). Код показанного примера я писал под tomcat 6.0.14 и перед размещением в internet проверил под jetty6 – все работало без проблем.

Надо сказать, что caucho разработала также и смежный с Hessian протокол Burlap (он в отличии от Hessian основан на xml), но какого-то глубокого смысла в использовании еще одного велосипеда я не нашел, к тому же поддержки сериализации flash-структур данных в этот формат в составе найденного на сайте caucho flash-клиента не было. К слову о клиенте Hessian, помимо flash версии библиотеки, есть и реализация Hessian для приложений построенных на базе javaFX.

Какие есть реализации Hessian на стороне сервера?



Естественно есть реализация для java (на ее примере я и буду дальше показывать, как создать Hessian-сервис). Надо сказать, что именно с java-реализацией я познакомился пару лет назад и использовал как альтернативу построения веб-сервисов между java-java (в условиях “закрытости” сервиса, особый бинарный формат Hessian был не существенен, а поддержка передачи сложных структур данных у него на высоте).

Есть и реализация hessian для php http://hessianphp.sourceforge.net/index.html. Библиотека развивалась в промежутке между 2004 и 2005 годами, с тех пор каких-либо значимых анонсов я не заметил. С другой стороны несколько тестовых приложений, которые я попробовал сделать в рамках изучения и ознакомления, работали без проблем.

С ruby и C# я никогда не работал, так что приведу просто ссылку на сайты соответствующих проектов (http://sourceforge.net/projects/hessianruby/ и http://www.hessiancsharp.org/). Возможно, там найдется и поддержка создания клиента для silverlight, но спекулировать на эту тему я не буду.

Далее я покажу пример создания на базе Hessian небольшого сервиса реализующего … да ничего не реализующего. Я полагаю, что читающие эту заметку вдоволь насмотрелись на мини-калькуляторы, мини-проверялки орфографии и т.д. Сервис должен передавать сложную структуру данных, java-сервис должен его получать, имитировать обработку и после изменения, отправить изменения обратно flash-клиенту.

Сначала вам нужно загрузить с сайта http://hessian.caucho.com/index.xtp свежую версию библиотеку для java стороны сервиса (я писал данный пример на основе hessian-3.1.3.jar от 2007.10.07, объем 144k), также flash библиотеку (я использовал hessian-flash-3.1.2.swc, объем 152k). Там же, на сайте, доступны их исходники.

Структуры данных на стороне java


  1. /**
  2.  * Объект элемент записной книжки
  3.  */
  4. public class Note implements Serializable {
  5.     public Integer id;
  6.     public String message;
  7.     public Document comments;
  8.  
  9.     public Note(Integer _id, String _message, Document _comments) {
  10.         this.id = _id;
  11.         this.message = _message;
  12.         this.comments = _comments;
  13.     }
  14. }
  15.  
  16.  
  17. /**
  18.  * Объект событие привязанное к некоторой записи в записной книжке
  19.  */
  20. public class NoteEvent implements Serializable {
  21.     public String message;
  22.     public Date dateof;
  23.     public Note friendNote;
  24.  
  25.     public NoteEvent(String _message, Date _dateof, Note _friendNote) {
  26.         this.message = _message;
  27.         this.dateof = _dateof;
  28.         this.friendNote = _friendNote;
  29.     }
  30. }
  31.  
  32.  
  33. /**
  34.  * Улучшенная заметка для записной книжки, добавлена поддержка "связанного" списка событий 
  35.  */
  36. public class SuperNote extends Note implements Serializable {
  37.     public ArrayList<NoteEvent> events;
  38.  
  39.     public SuperNote(Integer _id, String _message, Document _comments, ArrayList<NoteEvent> _events) {
  40.         super(_id, _message, _comments);
  41.         this.events = _events;
  42.     }
  43. }
  44.  
  45.  
  46. /**
  47.  * Объект записная книжка
  48.  */
  49. public class Notebook implements Serializable {
  50.     public ArrayList<Note> notes;
  51.  
  52.     public Notebook(ArrayList<Note> _notes) {
  53.         this.notes = _notes;
  54.     }
  55. }
Объект Notebook является контейнером для массива записей (Note или SuperNote). SuperNote производная от Note и добавляет (помимо простых полей своего предка) еще один контейнер для хранения событий привязанных к этой "Супер записи". Каждое событие (представлено классом NoteEvent) может содержать ссылку на еще один объект Note.

Особых ограничений на используемые типы данных hessian не накладывает. Так часть полей у меня “почти примитивы” (String, Integer), есть и более сложные (ArrayList) и объект XML Document. Важно, чтобы все типы данных, которые мы хотим передавать по сети, реализовывали интерфейс Serializable.

Теперь нужно разработать типы данных на стороне actionscript. Я создам те же четыре класса, помещу их в такой же пакет, как и java-код (model). Ну а говорить о том, что имена классов, имена полей совпадают, пожалуй не стоит – это очевидно (точнее "правильнее", хотя и не обязательно, далее я скажу об этом подробнее). Кода java-сервис выполняет отправку данных к flash-клиенту, то он отправляет вместе с данными и имена типов данных, на основании этих имен выполняется восстановление структуры.

Если два объекта, между которыми должна выполняться синхронизация, имеют “почти примитивный” тип данных (число, строка, массив), то проблем при передаче не возникает. Однако что будет в случае использования чего-то посложнее, чем строка или число? Как работает механизм чтения данных? Прочитав из потока информацию об объекта Hessian ищет тип данных с таким же названием как и передан с той стороны (благодаря одинаковому названию model.Note в java перейдет без проблем в model.Note в hessian). Но, если тип данных на стороне java или flash не был найден, то он заменяется на наиболее общий тип: для стороны java это объект HashMap (ассоциативный массив), для стороны flash – Object (с динамическим созданием полей с такими же именами, как были переданы).

Относительно того, что нет возможности полного преобразования между java и actionscript объектами, то больших проблем в этом я не нашел. С одной стороны завязывать код клиентского приложения на те структуры данных, которые возвращает сервис, не самая лучшая идея: это снижает гибкость приложения в ситуации когда “йо, мы забыли самое важное и теперь придется переделать структуру данных, передаваемую по сети”. Так что я всегда выделял отдельный код, который полученные структуры данных из java-сервиса конвертировал в actionscript-объекты (используемые для работы внутри приложения) и, наоборот. В любом случае выполнять подобные преобразования можно только в самом общем случае и при использовании чего-то сложнее(id, fio, birthday) придется “поработать напильником” (в конце-концов, это ведь два разных языка).

Как вывод: для передачи данных по сети необходимо тщательно очистить передаваемые классы от всего лишнего, заменить все нетривиальные типы данных на собственные аналоги.

Примеры actionscript-классов


  1. package model
  2. {
  3.   public class Note
  4.   {
  5.         // явное указание на тип данных который находится на той строне
  6. 	public var hessianTypeName : String = "model.Note";
  7.  
  8. 	// все поля которые мы хотим получить из java-сервиса должны быть public
  9. 	public var secret : String = "Супер-секрет о Flex";
  10. 	public var id : int;
  11. 	public var message : String;
  12. 	public var comments : Object;
  13.  
  14.  
  15. 	// всем классам которые будут передаваться по сети необходимо иметь конструктор без параметров
  16. 	public function Note (){}
  17.  
  18. 	public function __initNote(_id: int, _message: String, _comments: Object):void{
  19. 		this.id = _id;
  20. 		this.message = _message;
  21. 		this.comments = _comments;
  22. 	}
  23.   }
  24. }
  25.  
  26. package model
  27. {
  28.  
  29.   public class SuperNote extends Note
  30.   {
  31.  
  32. 	public var events : Array = new Array ();
  33.  
  34.         // явное указание на тип данных который находится на той строне
  35. 	public function SuperNote (){
  36. 		hessianTypeName = "model.SuperNote";
  37. 	}
  38.  
  39. 	public function __initSuperNote (_id: int, _message: String, _comments: Object, _events: Array):void{
  40. 		super.__initNote(_id,_message,_comments);
  41. 		events = _events;
  42. 	}
  43.   }
  44. }
  45.  
  46. package model
  47. {
  48.   public class NoteEvent
  49.   {
  50.         // явное указание на тип данных который находится на той строне
  51. 	public var hessianTypeName : String = "model.NoteEvent";
  52.  
  53. 	public var message : String;
  54. 	public var dateof : Object;
  55. 	public var friendNote : Note;
  56.  
  57. 	public function NoteEvent (){}
  58.  
  59. 	public function __initNoteEvent (_message: String, _dateof: Object, _friendNote : Note):void{
  60. 		this.message =_message;
  61. 		this.dateof = _dateof;
  62. 		this.friendNote = _friendNote;
  63. 	}
  64.   }
  65. }
  66.  
  67. package model
  68. {
  69.   import mx.collections.ArrayCollection;
  70.  
  71.   public class Notebook
  72.   {
  73.         // явное указание на тип данных который находится на той строне
  74. 	public var hessianTypeName : String = "model.Notebook";
  75.  
  76. 	public var notes : Array = new Array();
  77. 	public function Notebook (){}
  78.  
  79. 	public function __initNotebook(_notes: Array):void{
  80. 		this.notes = _notes;
  81. 	}		
  82.   }
  83. }
Обратите внимание на то, что тип данных для поля comments объекта Note имеет тип Object (никаких возможностей автоматически преобразовать xml в стиле java в xml в стиле actionscript, как я раньше и подчеркивал, нет). Да знаю я, знаю, это надуманный пример.

Также если в java-классе есть поля с модификаторами private то они все равно будут посланы по сети, а вот обратное не верно из flash уйдут на сервер только те поля, которые имеют модификатор public. В общем, подгоняем структуры данных и по модификаторам иначе операция чтения с последующим присвоением полей в flash-клиенте будет неудачной.

Критически важно: при передаче данных по сети для операции автоматического преобразования типов данных используются их имена, однако при передаче между flash и java. Возможны проблемы из-за того, что метод получения полностью квалифицированного имени класса в java возвращает имя в стиле пакет1.подпакет1.класс1, в то время как в flash в качестве разделителей имени класса и пакета выступает символ "::". Естественно, что выполнить такое преобразование не возможно (надо сказать что несколько раз я замечал как приходили имена от flash в стиле java, объяснения причины такому поведению у меня нет). В любом случае есть рекомендованная методика подсказки: если добавить в класс строкое поле с именем hessianTypeName и указать ему имя класса java на той строне, то все будет работать как часы.

Создаем java-сервис



Для создания сервиса, прежде всего, необходимо определить интерфейс с перечислением методов, которые мы хотим сделать “доступными”. Например:
  1. public interface ILifeSupport {
  2.     Notebook doAnything (String whatDoYourWant, Notebook notebook) throws ServletException, MegaException;
  3.     String reverseString(String strForReverse);
  4. }
В составе интерфейса два метода: простенький reverseString (строка на вход и строка на выход) и более сложный doAnything, получающий два параметра один из которых объект Notebook.

Теперь нужно написать код сервлета, реализующего данную функциональность. В следующем примере я создал свой класс сервлет, как наследник от HessianServlet, но в случае необходимости и этого требования можно избежать.
  1. package helpers;
  2.  
  3. import com.caucho.hessian.server.HessianServlet;
  4. import model.Note;
  5. import model.NoteEvent;
  6. import model.Notebook;
  7. import model.SuperNote;
  8. import org.w3c.dom.Document;
  9. import org.w3c.dom.Element;
  10.  
  11. import javax.servlet.ServletConfig;
  12. import javax.servlet.ServletException;
  13. import javax.xml.parsers.DocumentBuilder;
  14. import javax.xml.parsers.DocumentBuilderFactory;
  15. import javax.xml.parsers.ParserConfigurationException;
  16. import java.util.ArrayList;
  17. import java.util.Date;
  18.  
  19.  
  20. public class NotebookServerImpl extends HessianServlet implements ILifeSupport {
  21.  
  22.     public void init(ServletConfig servletConfig) throws ServletException {
  23.         super.init(servletConfig);
  24.     }
  25.  
  26.  
  27.     /**
  28.      * Метод, который выполняет какую-то важную задачу улучшения жизни
  29.      *
  30.      * @param whatDoYourWant Запрос на то что мы хотим улучшить
  31.      * @param notebook       Содержимое записной книжки желающего улучшений
  32.      * @return Улучшенная записная книжка
  33.      */
  34.     public Notebook doAnything(String whatDoYourWant, Notebook notebook) throws ServletException, MegaException {
  35.         if (whatDoYourWant.indexOf("Счастье") >= 0)
  36.             throw new IllegalArgumentException("Счастья нет");
  37.         if (whatDoYourWant.indexOf("Горе") >= 0)
  38.             throw new MegaException("И горя тоже нет");
  39.  
  40.  
  41.         DocumentBuilder builder = null;
  42.         try {
  43.             builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
  44.         }
  45.         catch (ParserConfigurationException ce) {
  46.             throw new ServletException(ce);
  47.         }
  48.         // -- start build xml
  49.         Document doc = builder.newDocument();
  50.         final Element megaroot = doc.createElement("MegaRoot");
  51.         final Element simplenode = doc.createElement("SimpleNode");
  52.         doc.appendChild(megaroot);
  53.         simplenode.setAttribute("IsHappy", "Да");
  54.         megaroot.appendChild(doc.createCDATASection("Основы экономики"));
  55.         megaroot.appendChild(simplenode);
  56.         // -- end build xml
  57.  
  58.         ArrayList<Note> notes = new ArrayList<Note>();
  59.         final Note note_lenka = new Note(1, "Привет, Ленка", doc);
  60.         notes.add(note_lenka);
  61.  
  62.         ArrayList<NoteEvent> events = new ArrayList<NoteEvent>();
  63.         // у элемента NoteEvent есть ссылка на запись Note принадлежащую Ленке
  64.         final NoteEvent note_tanka = new NoteEvent("Купить чайник", new Date(), note_lenka);
  65.         events.add(note_tanka);
  66.         notes.add(new SuperNote(2, "Привет, Танька", doc, events));
  67.         return new Notebook(notes);
  68.     }
  69.  
  70.  
  71.     public String reverseString(String strForReverse) {
  72.         // переворачивание строки, я только сейчас понял, что у java нет метода reverse для строки,
  73.         // нужно создавать StringBuffer, как это ужасно
  74.         StringBuffer buf = new StringBuffer(strForReverse);
  75.         return buf.reverse().toString();
  76.     }
  77. }
В дескриптор развертывания я добавил маппинг для созданного выше сервлета:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" 
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee     
  5. http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  6.     <servlet>
  7.         <servlet-name>NotebookServer</servlet-name>
  8.         <servlet-class>helpers.NotebookServerImpl</servlet-class>
  9.     </servlet>
  10.  
  11.     <servlet-mapping>
  12.         <servlet-name>NotebookServer</servlet-name>
  13.         <url-pattern>/hessian/notes/*</url-pattern>
  14.     </servlet-mapping>
  15. </web-app>
Далее я покажу пример связывания между java-java. Чтобы показать, что мы можем корректно передавать не только сложные структуры данных, но и корректно обрабатываются выброшенные исключения я задекларировал две исключительные ситуации (IllegalArgumentException, MegaException). Они бросаются, если в первом параметре метода doAnything переданы строки содержащие слова “Счастье” и “Горе” (во втором случае бросается пользовательское исключение).
  1. public class MegaException extends Exception{
  2.     public MegaException(String message) {
  3.         super(message);
  4.     }
  5. }

Тестируем созданный сервис, пока из java-клиента



Теперь пример кода, который тестирует работу сервиса (java -> java). В практике это неплохой вариант: сначала протестировать функциональность проекта, используя для вызова java-клиент, а затем переносить код на flash-клиента.
  1. package test;
  2.  
  3. import com.caucho.hessian.client.HessianProxyFactory;
  4. import helpers.ILifeSupport;
  5. import helpers.MegaException;
  6. import model.Note;
  7. import model.NoteEvent;
  8. import model.Notebook;
  9. import model.SuperNote;
  10. import static org.junit.Assert.*;
  11. import org.junit.BeforeClass;
  12. import org.junit.Test;
  13. import org.w3c.dom.Document;
  14. import org.w3c.dom.Element;
  15.  
  16. import javax.servlet.ServletException;
  17. import javax.xml.parsers.DocumentBuilder;
  18. import javax.xml.parsers.DocumentBuilderFactory;
  19. import javax.xml.parsers.ParserConfigurationException;
  20. import java.net.MalformedURLException;
  21. import java.util.ArrayList;
  22. import java.util.Date;
  23.  
  24.  
  25. public class TestService {
  26.  
  27.     private static ILifeSupport ilife;
  28.     private static Notebook notebookDemo;
  29.  
  30.     @BeforeClass
  31.     public static void setUp() throws MalformedURLException, ParserConfigurationException {
  32.         // выполняем подготовку класса для теста (создаем сервис подключенный к hessian, 
  33.         // а таккже заготовку объекта Notebook)
  34.         String url = "http://center:8080/notes/hessian/notes";
  35.  
  36.         HessianProxyFactory factory = new HessianProxyFactory();
  37.         // запросили создание сервиса располженного по адресу url и реализующего интерфейс ILifeSupport
  38.         ilife = (ILifeSupport) factory.create(ILifeSupport.class, url);
  39.  
  40.         DocumentBuilder builder = null;
  41.         builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
  42.         // -- start build xml
  43.         Document doc = builder.newDocument();
  44.         final Element megaroot = doc.createElement("MegaRoot");
  45.         final Element simplenode = doc.createElement("SimpleNode");
  46.         doc.appendChild(megaroot);
  47.         simplenode.setAttribute("IsHappy", "Да");
  48.         megaroot.appendChild(doc.createCDATASection("Основы экономики"));
  49.         megaroot.appendChild(simplenode);
  50.         // -- end build xml
  51.  
  52.         Note note_vasya = new Note(1, "Заметки о Васе", doc);
  53.         ArrayList<NoteEvent> events = new ArrayList<NoteEvent>();
  54.         events.add(new NoteEvent("Сообщение об чем-то важном", new Date(), note_vasya));
  55.         SuperNote note_petya = new SuperNote(2, "Заметки о Пете", doc, events);
  56.  
  57.         final ArrayList<Note> listNotes = new ArrayList<Note>();
  58.         listNotes.add(note_vasya);
  59.         listNotes.add(note_petya);
  60.         notebookDemo = new Notebook(listNotes);
  61.     }
  62.  
  63.     @Test
  64.     public void testSimpleString() {
  65.         // тест на простенькое переворачивание строки
  66.         assertEquals("яицативарГ", ilife.reverseString("Гравитация"));
  67.     }
  68.  
  69.     @Test
  70.     public void testReferences() throws MegaException, ServletException {
  71.         // проверка на то что корректно передаются рекурсивные структуры данных
  72.         Notebook book = ilife.doAnything("Слоники на водопаде", notebookDemo);
  73.         SuperNote snote = (SuperNote) book.notes.get(1);
  74.         assertSame(snote.events.get(0).friendNote, book.notes.get(0));
  75.     }
  76.  
  77.  
  78.     @Test
  79.     public void testNotebookService() throws ServletException, MegaException {
  80.         // проверка полученного объекта notebook, включает в себя тест по количество полученных записей в книжке
  81.         Notebook book = ilife.doAnything("Слоники на водопаде", notebookDemo);
  82.         assertEquals(book.notes.size(), 2);
  83.         assertEquals(1, (long) book.notes.get(0).id);
  84.         assertEquals(2, (long) book.notes.get(1).id);
  85.         // также проверяем правильность передачи иерархии классов
  86.         assertTrue(book.notes.get(1) instanceof SuperNote);
  87.         final SuperNote snote = (SuperNote) book.notes.get(1);
  88.         // и тестируем как передалось такое "тяжеловесное образование" как XML Document
  89.         assertEquals("MegaRoot", snote.comments.getDocumentElement().getNodeName());
  90.         assertEquals("SimpleNode", snote.comments.getDocumentElement().getLastChild().getNodeName());
  91.         assertEquals("Да", 
  92.           snote.comments.getDocumentElement().getLastChild().
  93.           getAttributes().getNamedItem("IsHappy").getNodeValue());
  94.     }
  95.  
  96.  
  97.     @Test(expected = IllegalArgumentException.class)
  98.     public void testNotebookServiceMustFail_1() throws ServletException, MegaException {
  99.         // тест на выбрасывание исключения нужного типа
  100.         Notebook book = ilife.doAnything("Счастье", notebookDemo);
  101.         assertTrue(false);
  102.     }
  103.  
  104.     @Test(expected = MegaException.class)
  105.     public void testNotebookServiceMustFail_2() throws ServletException, MegaException {
  106.         // еще один тест на выбрасывание исключения нужного типа
  107.         Notebook book = ilife.doAnything("Горе", notebookDemo);
  108.         assertTrue(false);
  109.     }
  110. }

Flex-клиент



Теперь переходим к flex и форму с кнопкой “послать” и текстовой надписью Label, в которую я буду выводить размер отправляемых данных полученных путем сериализации в ByteArray.

Какой-то особой обработки принятых данных я делать не буду: просто сделаю скриншот окна debugger-а, чтобы показать, во что превратились принятые данные.

Вот код клиента:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" 
  3. 	implements="mx.rpc.IResponder"
  4. 	applicationComplete="onAppCreationComplete ()"
  5. 	>
  6. <mx:Script>
  7. <![CDATA[
  8.   import mx.events.ResizeEvent;
  9.   import mx.rpc.events.ResultEvent;
  10.   import model.NoteEvent;
  11.   import model.SuperNote;
  12.   import model.Note;
  13.   import model.Notebook;
  14.   import mx.rpc.AsyncToken;
  15.   import mx.controls.Alert;
  16.   import hessian.client.HessianService;
  17.  
  18.   private var service: HessianService;
  19.   // ссылка на объект удаленного сервиса hessian
  20.   private function onAppCreationComplete ():void{
  21. 	service = new HessianService ("http://center:8080/notes/hessian/notes");
  22.         // создадим сервис указав как параметр путь к его реализации
  23.   }
  24.  
  25.   public function fault(_evt:Object):void{
  26.     // обработка ошибки при вызове удаленного сервиса
  27.     mx.controls.Alert.show("fail: "+ _evt); 
  28.   }
  29.  
  30.   public function result(_evt:Object):void{
  31.      // метод вызываемый при условии успешного завершения вызова
  32.      var evt : ResultEvent  = _evt as ResultEvent;
  33.      mx.controls.Alert.show("ok: "+evt.result + " : "+ 
  34.      // сравним правильно ли была передана иерархия взаимосвязанных объектов
  35.      // нет, не правильно. Объект-запись под номером 0 был восстановлена два раза. 
  36.      // Надо сказать что это поведение несколько не устойчиво, и пару раз восстановление проводилось успешно,
  37.      // точной зависимости почему и как у меня. поэтому я решил полагаться на худшую ситуацию: 
  38.      // рекурсия корректно не обрабатывается.
  39.      (evt.result.notes[1].events[0].friendNote == evt.result.notes[0])
  40.      );
  41.    }
  42.  
  43.    private function doSend ():void{
  44. 	var x : XML = 
  45. 	   <Special>
  46. 	      <Personal />
  47. 	      <User id="1" name="Jim" />
  48. 	      <Message>Привет, Джим</Message>
  49. 	   </Special>;
  50.  
  51. 	   //всякая попытка передать сложную структуру данных 
  52. 	   //(имеющую рекурсивые отношения) приводит к переполнению стека
  53. 	   var IF_MAKE_ERROR : Boolean = false;
  54. 	   if (! IF_MAKE_ERROR)
  55. 		x = null; 
  56.  
  57. 	   var note_1 : SuperNote = new SuperNote ();
  58. 	   note_1.__initSuperNote(1, "Привет, Вася", x, []);
  59.  
  60. 	   var note_2 : Note = new Note ();
  61. 	   note_2.__initNote(2, "Привет, Ленка", x);
  62.  
  63. 	   var event_1 : NoteEvent = new NoteEvent();
  64. 	   event_1.__initNoteEvent('Привет, Петька', new Date (), IF_MAKE_ERROR?note_1:null);
  65. 	   note_1.events.push(event_1);
  66.  
  67. 	   var notebook : Notebook = new Notebook ();
  68. 	   notebook.__initNotebook([note_1, note_2]);
  69.  
  70. 	   //теперь оценим размер передаваемых данных:
  71. 	   var bout : ByteArray   = new ByteArray  ();
  72. 	   bout.writeObject(notebook);
  73. 	   lab_out_size.text =  ""+bout.length;
  74.  
  75. 	   // делаем вызов (имя операции doAnything)
  76. 	   var token:AsyncToken =  service.doAnything.send("Запрос на улучшение жизни", notebook);
  77.            // указываем объект отвественный за обработку событий (был ли успешен вызов или нет?)
  78. 	   token.addResponder(this);
  79.    }
  80. ]]>
  81. </mx:Script>
  82.  
  83. <mx:VBox width="100%" height="100%" paddingBottom="20" paddingLeft="20" paddingRight="20" paddingTop="20">
  84.    <mx:HBox width="100%">
  85. 	<mx:Button label="Отправить набор данных" click="doSend ()" />
  86. 	<mx:Label text="Размер отправляемых данных: "/>
  87. 	<mx:Label id="lab_out_size"  />
  88.    </mx:HBox>
  89. </mx:VBox>
  90. </mx:Application>
Обратите внимание на то, что добавлена переменная IF_MAKE_ERROR. В случае если ее значение true, то в отправляемую на сервер информацию добавляются данные “рекурсивной структуры”. Если так сделать, то Hessian увлекается спуском по стеку и генерируется исключение stack overflow.



Интересно, что и обратная операция восстановления рекурсивной структуры данных проходит не совсем хорошо: если в потоке пришло один объект на который есть две ссылки из других объектов, то при восстановлении будет создана вторая копия объекта. (см. метод result).

Вот так выглядит пример отправляемых по сети данных:



А вот так выглядят полученные данные на строне flash:



И пример полученных данных на строне java:



Обратите внимание на то, что тип данных дата был восстановлен корректно, также как и остальные "примитивные типы данных" (автоматически и без подсказок о правилах маппинга).

Categories: Flash & Flex