Календарь на Апрель 2024 года: calendar2008.ru/2024/aprel/
Навигация
Главная »  Xml 

Web-сервисы Java, часть 3: Связывание данных в Axis2 (исходники)


Источник: IBM developerWorks Россия
Денис Сосноски
Несмотря на то, что обмен сообщениями XML лежит в основе Web-сервисов, большинство приложений Web-сервисов не имеют отношения к XML. Вместо этого приложения обмениваются бизнес-данными, специфичными для каждого конкретного приложения. В данном случае XML - это просто формат, используемый для представления бизнес-данных, поддерживающий интерфейс Web-сервиса. XML отлично подходит для этой цели, поскольку он предоставляет платформенно-независимое представление данных, которое может обрабатываться различными инструментами. Однако приложениям в конечном счете приходится преобразовывать XML в собственные внутренние структуры данных, используемые в приложении, и обратно.

Связыванием данных называют методики преобразования между XML и структурами данных приложения. Можно написать собственный код связывания данных для приложения, но большинство разработчиков предпочитают работать со средами связывания данных, которые выполняют преобразование типовым образом, применимым к широкому спектру приложений. Одной из основных сильных сторон среды Web-сервисов Apache Axis2 является то, что она с самого начала разрабатывалась для работы со множеством сред связывания данных. Вы можете выбрать тот подход к связыванию данных, который более всего подходит к вашим потребностям, и использовать этот подход для обработки преобразований между XML и структурами данных, используя при этом среду Axis2 (и ее расширения) для управления работой собственно Web-сервиса.

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

Ссылки на Axis2

Из предыдущей статьи этой серии вы узнали о модели документов AXIOM, которую Axis2 использует для обработки сообщений XML. AXIOM отличается от других моделей документов тем, что она поддерживает формирование модели не только целиком, но и по необходимости. При использовании среды связывания данных для преобразования XML в структуры данных приложения и наоборот связываемый с данными XML обычно является лишь частью модели документа AXIOM. Модель не расширяется до полной модели документа до тех пор, пока это не понадобится по какой-либо причине (например, для шифрования или подписывания документов с помощью WS-Security).

Для того чтобы отделить приложение от работы с AXIOM напрямую, Axis2 поддерживает генерацию кода связи на основе описания сервиса на языке описания Web-сервисов (Web Services Description Language, WSDL). Сформированный код связи выполняет преобразование структур данных в XML и обратно, используя выбранную вами среду связывания данных, предоставляя вашему приложению прямой доступ к структурам данных. Кроме того, в Axis2 реализована ограниченная поддержка обратного процесса - формирования описания WSDL из существующего кода.

Axis2 формирует код связи как для клиентов сервисов, так и для сервис-провайдеров. Клиентский код связи - своего рода класс-заглушка, расширяющий класс org.apache.axis2.client.Stub Axis2. Код связи поставщика (серверный код) принимает форму скелета реализации конкретного сервиса, с классом-приемником сообщений, в котором реализован интерфейс org.apache.axis2.engine.MessageReceiver. Формирование кода для клиента и сервера выполняется с помощью инструмента WSDL2Java. После этого мы рассмотрим фактический код связи, перейдем к подробностям использования инструмента WSDL2Java и закончим этот раздел кратким обзором способов использования существующего кода.

Код связи клиента

Код заглушки (stub) с клиентской стороны определяет методы доступа, которые будет использовать код вашего приложения для вызова операций сервиса. Сначала мы создаем экземпляр класса заглушки - либо с помощью конструктора по умолчанию (если конечная точка вашего сервиса всегда будет такой же, какая определена в WSDL, используемом для формирования заглушки), или с помощью конструктора, принимающего в качестве строкового параметра иную конечную точку. После того как мы создали экземпляр заглушки, мы можем по желанию использовать методы, определенные базовым классом org.apache.axis2.client.Stub, для настройки различных параметров. После этого мы можем вызвать методы доступа, зависящие от сервиса, для фактического вызова операций.

В Листинге 1 показан пример создания заглушки с изменением конечной точки сервиса (по сравнению с указанной в WSDL) на порт клиентской системы (localhost) Tcpmon по умолчанию (8800). Tcpmon - это очень популярный инструмент мониторинга обмена сообщениями Web-сервиса, поэтому наличие такой возможности в клиентском коде часто бывает очень полезным. После создания экземпляра заглушки изменяется значение тайм-аута, назначаемое по умолчанию (также полезно при отладке кода провайдера, поскольку вы можете превысить стандартное значение в 20 секунд), и вызывается метод сервиса.

Листинг 1. Пример кода заглушки с клиентской стороны
 LibraryStub stub = new LibraryStub("http://localhost:8800/axis2/services/library"); stub.getServiceClient().getOptions().setTimeoutInMilliseconds(10000000); Types[] types = stub.getTypes(); 

В Листинге 1 показан синхронный вызов метода сервиса, при котором клиентский поток блокируется вызовом сервиса и не возвращается до завершения вызова и получения результатов. В Axis2 также поддерживаются асинхронные вызовы с помощью интерфейса обратного вызова. В листинге 2 показан модифицированный код Листинга 1, куда добавлен обычный асинхронный вызов (обычный в том смысле, что код приложения просто ждет завершения операции, а не делает чего-либо полезного).

Листинг 2. Асинхронный код клиентской заглушки
 LibraryStub stub = new LibraryStub("http://localhost:8800/axis2/services/library"); TypesCallback cb = new TypesCallback(); stub.startgetTypes(cb); Type[] types; synchronized (cb) { while (!cb.m_done) { try { cb.wait(); } catch (Exception e) {} } types = cb.m_result; if (types == null) { throw cb.m_exception; } } ... private static class TypesCallback extends LibraryCallbackHandler { private boolean m_done; private Types[] m_result; private Exception m_exception;              public synchronized void receiveResultgetTypes(Type[] resp) { m_result = resp; m_done = true; notify(); }              public synchronized void receiveErrorgetTypes(Exception e) { m_exception = e; m_done = true; notify(); } } 

По соединению HTTP, использованному в Листинге 2, ответ обычно возвращается клиенту немедленно. Асинхронные вызовы наиболее полезны при работе по транспорту, который разделяет потоки запроса и ответа - например, Java™ Message Service (JMS) или Simple Mail Transfer Protocol (SMTP) -, поскольку между отправлением запроса и получением ответа могут быть значительные временные задержки. Конечно же, сервисы, работающие по HTTP, также могут иметь значительные задержки обработки. Для работы с сервисами HTTP с такими задержками можно использовать WS-Addressing, который позволяет отделить ответы от запросов, а для обработки этих ответов использовать асинхронные вызовы.

Кроме класса заглушки (и класса обработки обратного вызова, если вы используете поддержку асинхронного обмена данными), формируется еще один интерфейс для вашего клиентского кода. Интерфейс определяет методы сервиса, соответствующие операциям, определяемым конструкцией WSDL portType. Этот интерфейс реализован в заглушке, которая также добавляет несколько специализированных методов для внутреннего использования. Вы можете либо работать с заглушкой напрямую, как показано в Листинге 1 и Листинге 2, или просто использовать этот интерфейс для просмотра методов, входящих в состав описания сервиса. В любом случае когда вы вызываете один из методов сервиса, заглушка обрабатывает преобразование объектов данных запросов в XML, а также возвращенного XML в объекты данных ответов с помощью выбранной вами среды связывания данных.

Если вы просто хотите работать с XML на клиентской стороне, вам не нужно использовать сформированный клиентский класс stub совсем; вместо этого вы можете использовать класс org.apache.axis2.client.ServiceClient. Это означает, что для начала нам нужно настроить сервис и операцию, после чего вызвать метод ServiceClient.createClient() для создания org.apache.axis2.client.OperationClient операции. Для удобства инструмент WSDL2Java (будет описываться позже в этой статье) предоставляет возможность создания класса заглушки, даже если вы работаете напрямую с XML. Сформированный класс заглушки выглядит большей частью так же, как и для примеров со связыванием данных, с тем лишь исключением, что он работает с элементами AXIOM, а не с объектами данных.

Серверный код связи

Серверный код связи для Axis2 представляет собой приемник сообщений, определенный в рамках конфигурации сервиса Axis2. В этом приемнике сообщений должен быть реализован интерфейс org.apache.axis2.engine.MessageReceiver. В интерфейсе определен единственный метод void receive(org.apache.axis2.context.MessageContext) . Среда Axis2 вызывает этот метод при получении сообщения запроса, после чего этот метод выполняет всю обработку запроса (в том числе и формирование ответа, если это необходимо).

Если вы работаете напрямую с XML (в виде элементов AXIOM), вы можете использовать один из стандартных классов org.apache.axis2.receivers.RawXML*MessageReceiver для связи сервера (здесь * описывает тип обмена сообщениями, используемый сервисом). В противном случае вы используете сгенерированный класс приемника сообщений, который выполняет согласование интерфейса Axis2, построенного на базе AXIOM, и кода сервиса, использующего объекты данных. Код сервиса формируется в виде скелетной реализации, при которой методы сервиса просто выдают сигнал исключения. Для завершения серверной обработки вам нужно добавить в этот скелет собственный код.

В Листинге 3 показан пример серверного скелета (формат изменен для удобства чтения), где метод getBook() оставлен в неизменном виде, а метод getTypes() передает запрос фактическому классу.

Листинг 3. Пример серверного скелета
 public class LibrarySkeleton { private final LibraryServer m_server;      public LibrarySkeleton() { m_server = new LibraryServer(); }      /** * Автоматический созданная подпись метода * * @param isbn * @return book value */ public com.sosnoski.ws.library.Book getBook(java.lang.String isbn) { //Заполнить необходимой логикой throw new java.lang.UnsupportedOperationException("Please implement " + this.getClass().getName() + "#getBook"); }  /** * Получение типов книг, входящих в библиотеку. * * @return types */ public com.sosnoski.ws.library.Type[] getTypes() { return m_server.getTypes(); } } 

Издержки добавления кода непосредственно в этот класс заключаются в том, что при изменении интерфейса сервиса вам придется сгенерировать класс еще раз и снова внести изменения. Этого можно избежать с помощью отдельного класса, который дополняет сформированный скелет, позволяя переписывать методы скелета без изменения сформированного кода. Для этого вам нужно изменить сгенерированное описание сервиса services.xml. Это делается просто; достаточно заменить название класса скелета на название класса конкретной реализации. Во всех рассматриваемых ниже в этой статье примерах связывания данных используется подход с отдельным классом реализации. Эти примеры можно увидеть в файлах Ant build.xml в разделе файлы для загрузки, где показано, как автоматизировать замену.

Инструменты Axis2

Axis2 предоставляет разработчикам ряд инструментов, облегчающих работу со средой. Наиболее важными из них являются инструменты, которые позволяют формировать код связи Java (описанный в предыдущем разделе) из определения сервиса WSDL и формировать определение сервиса WSDL на основе существующего кода Java.

Получение кода из WSDL

В состав Axis2 входит инструмент WSDL2Java, служащий для формирования кода из описания сервиса WSDL. Вы можете использовать этот инструмент напрямую, запуская класс org.apache.axis2.wsdl.WSDL2Java из приложения Java, или посредством задачи Ant, подключаемого модуля Maven или подключаемых модулей Eclipse или IDEA. Недостаток такого большого числа вариантов состоит в том, что большинство альтернатив обычно отстают от основного приложения Java в реализации новых возможностей и исправлении ошибок, поэтому лучше всего использовать непосредственный вызов приложения Java (описываемый в этой статье).

В инструменте WSDL2Java реализовано множество параметров командной строки, и их число постоянно растет. В документации по Axis2 содержится полный список этих параметров, поэтому здесь мы рассмотрим только наиболее важные из их числа:

  • -o path - Определяет директорию, в которой будут создаваться классы и файлы (по умолчанию используется рабочая директория)
  • -p package-name - Определяет пакет, в который будут записываться формируемые классы (по умолчанию используется пространство имен WSDL)
  • -d name - Определяет среду связывания данных ( adb для ADB, xmlbeans для XMLBeans, jibx для JiBX и none для отключения связывания данных; по умолчанию используется adb )
  • -uw - Распаковывает упакованные документально-литеральные сообщения, только для поддерживаемых сред (на сегодняшний день это ADB и JiBX)
  • -s - Формирует только синхронный клиентский интерфейс
  • -ss - Формирует серверный код
  • -sd - Формирует файлы для установки на сервере
  • -uri path - Задает путь к WSDL для формируемого сервиса
Также в WSDL2Java реализовано несколько параметров, специфичных для отдельных сред связывания данных. Вы можете увидеть часть из этих параметров, когда перейдете к примерам связывания данных ниже в этой статье.

Формирование WSDL из кода

В состав Axis2 также входит инструмент Java2WSDL, который вы можете использовать для создания определения сервиса WSDL на основе существующего кода сервиса. Однако полезность этого инструмента страдает от множества ограничений, в том числе невозможности работы с классами коллекций Java и отсутствия гибкости при структурированию XML, формируемого классами Java. Отчасти эти ограничения связаны с недостаточным интересом к этой области вследствие изменения способов разработки Web-сервисов.

Вообще говоря, многие авторитеты в области Web-сервисов и SOA не одобряют разработку Web-сервисов на основе существующего кода. Они считают, что использование кода привязывает структуру сообщений XML к определенной реализации, тогда как принцип Web-сервисов заключается в том, что XML не должен зависеть от реализации. Конечно же, у этой точки зрения есть некоторые основания, но и у ее противников также достаточно аргументов. Один из них состоит в сложности написания определений сервисов WSDL и схем XML с нуля. И WSDL, и схема представляют собой достаточно сложные стандарты, и для эффективного применения существующих инструментов для работы с этими определениями требуется хорошее знание этих стандартов. Если их использует разработчик, не разбирающийся в стандартах, получающиеся описание WSDL и схемы часто бывают значительно более запутанными, чем сформированные из кода. Еще одна проблема носит чисто практический характер. Часто у разработчиков есть код, реализующий необходимые функции, который должен быть выполнен в виде Web-сервиса, и они желают использовать этот код без внесения значительных изменений. Поэтому формирование WSDL на основе кода, вероятно, будет в обозримом будущем оставаться проблемой.

В качестве более мощной альтернативы Java2WSDL вы можете попробовать использовать разработанный мной инструмент Jibx2Wsdl. Jibx2Wsdl формирует полный комплект определений, связывающих WSDL, схемы и JiBX на основе одного или нескольких классов. Он поддерживает общие коллекции и перечисления Java 5, сохраняя при этом совместимость с более ранними версиями виртуальных машин Java (JVM), а также автоматически экспортирует Javadocs из исходных файлов Java в качестве документации для формируемых определений WSDL и схемы. В Jibx2Wsdl также реализован расширенный механизм настройки, позволяющий управлять тем, как будут определяться сервисы и представление XML из классов Java, что позволяет использовать типизированные данные даже в коллекциях Java версий ниже 5. Несмотря на то, что Jibx2Wsdl специально предназначен для упрощения развертывания существующих классов в качестве Web-сервисов с помощью среды связывания данных JiBX (также созданной мной), сформированные описания WSDL и схемы не зависят от среды связывания данных. Вы можете использовать их с другими средами связывания данных Java или даже с другими платформами - просто сформируйте все, что нужно, отбросьте связывание JiBX и работайте с остальным.

Еще одной альтернативой для пользователей Java 5 или более поздних версий являются аннотации Java Architecture for XML Binding (JAXB) 2.0 и Java API for XML Web Services (JAX-WS), которые позволяют развернуть объекты данных и сервисные классы в качестве Web-сервисов. Эти аннотации не предоставляют такого же уровня возможностей настройки, какими отличается Jibx2Wsdl, но они позволяют вам встроить информацию о конфигурации непосредственно в исходный код, что некоторые разработчики находят привлекательным. В Axis2 реализована экспериментальная поддержка JAXB 2.0 и JAX-WS начиная с версии 1.2, и в дальнейших версиях она будет улучшаться. Кроме того, в будущих версиях Jibx2Wsdl также может быть реализована поддержка аннотаций JAXB 2.0 и JAX-WS в виде дополнительных настроек. (В следующей статье этой серии будут подробно рассматриваться JAXB 2.0 и JAX-WS, и мы еще раз вернемся к вопросу формирования WSDL из кода.)

Сравнение различных подходов к связыванию данных

В Axis2 (начиная с версии 1.2) реализована полная поддержка трех вариантов связывания данных и готовится поддержка еще нескольких. В этой статье сравниваются примеры кода, использующие три полностью поддерживаемые среды связывания данных и раскрываются некоторые сильные и слабые стороны использования каждой из этих сред с Axis2.

Пример кода, показанный в Листинге 4 (также входит в состав примеров, которые можно загрузить в разделе файлы для загрузки), реализует сервис библиотеки, в которой хранится набор книг, организованных по тематикам. В библиотеке определены несколько операций, в том числе:

  • getBook
  • getTypes
  • addBook
  • getBooksByType
В листинге 4 представлен фрагмент WSDL для этого сервиса, в котором показаны только части, участвующие в работе getBook.

Листинг 4. Сервис библиотеки WSDL
                                              ...                              ...                   ...          ...                                           ...   ...   

Фактический код реализации сервиса прост, он наполняет экземпляр библиотеки жестко зафиксированным списком книг. Клиентский код выполняет следующую последовательность запросов:
  1. getBook
  2. getTypes
  3. Пара запросовaddBook; второй из них возвращает SOAP Fault в результате попытки добавления дублирующегося идентификатора книги
  4. getBooksByType
Детали реализации различны в разных примерах, поскольку в каждом из них используются объекты данных, соответствующие объектам, участвующим в конкретном связывании данных. Если не указано иное, весь представленный код совместим с версиями Axis2 1.1.1 и 1.2. Для версии Axis2 1.3, готовящейся к выходу на момент публикации этой статьи, требуется незначительное изменение кода вследствие изменения названий формируемых классов исключений, соответствующих сбоям в работе сервиса. Обе версии кода доступны для загрузки (см. раздел файлы для загрузки). В этой статье мы рассмотрим только клиентский код, хотя загрузить можно (см. раздел файлы для загрузки) и клиентский, и серверный код вместе с файлами компиляции Ant для всех примеров. После этого мы сравним клиентский код для трех сред связывания данных и рассмотрим сильные и слабые стороны каждого подхода.

Axis2 Data Binding

ADB (Связывание данных Axis2) - это расширение Axis2 для связывания данных. В отличие от других сред связывания данных, код ADB можно использовать только вместе с Web-сервисами Axis2. Это обстоятельство значительно ограничивает использование ADB, однако оно также даёт определенные преимущества. Поскольку ADB интегрировано с Axis2, код может быть оптимизирован под требования Axis2. В качестве одного из примеров можно назвать то, что ADB построено на модели документа AXis Object Model (AXIOM), которая является основой Axis2 (как обсуждалось в предыдущей статье этой серии). Кроме того, в ADB реализованы некоторые полезные функции, отсутствующие на данный момент в других средах связывания данных, в том числе автоматическая обработка вложений. В WSDL2Java реализована полная поддержка формирования кода ADB, в том числе формирование классов модели данных, соответствующих компонентам схемы XML.

Поддержка схем в ADB имеет некоторые ограничения. В текущей версии Axis2 1.2 к этим ограничениям относятся такие возможности работы со схемами, как компоновщики с maxOccurs="unbounded", определения схемы с attributeFormDefault="qualified" и другие похожие вариации. Однако поддержка схем в ADB Axis2 1.2 значительно лучше, чем в версии Axis2 1.1, и эта поддержка, скорее всего, будет продолжать развиваться с каждой новой версией среды Axis2 до тех пор, пока не будут поддерживаться все основные функции схем.

В базовом варианте формирования кода ADB используется прямая модель, в которой каждому входящему и исходящему сообщению, используемым в каждой операции, назначается отдельный класс. В Листинге 5 показаны наиболее интересные фрагменты клиентского кода примера формирования кода ADB таким образом. В клиентском коде видно взаимодействие с классами, созданными ADB, к которым относятся классы GetBookDocument и GetBookDocument.GetBook, используемые в качестве параметра при вызове метода getBook(), а также классы GetBookResponseDocument и BookInformation, используемые для получения результатов этого вызова.

Листинг 5. Клиентский код ADB
 // создание клиентской заглушки AdbLibraryStub stub = new AdbLibraryStub(target);          // получение директории книг String isbn = "0061020052"; GetBook gb = new GetBook(); gb.setIsbn(isbn); GetBookResponse gbr = stub.getBook(gb); BookInformation book = gbr.getGetBookReturn(); if (book == null) { System.out.println("No book found with ISBN '" + isbn + '\''); } else { System.out.println("Retrieved '" + book.getTitle() + '\''); }          // получение списка определеных типов GetTypesResponse gtr = stub.getTypes(new GetTypes()); TypeInformation[] types = gtr.getGetTypesReturn(); System.out.println("Retrieved " + types.length + " types:"); for (int i = 0; i < types.length; i++) { System.out.println(" '" + types[i].getName() + "' with " + types[i].getCount() + " books"); }          // добавление новой книги String title = "The Dragon Never Sleeps"; isbn = "0445203498"; try { AddBook ab = new AddBook(); ab.setType("scifi"); ab.setAuthor(new String[] { "Cook, Glen" }); ab.setIsbn(isbn); ab.setTitle(title); stub.addBook(ab); System.out.println("Added '" + title + '\''); ab.setTitle("This Should Not Work"); stub.addBook(ab); System.out.println("Added duplicate book - should not happen!"); } catch (AddDuplicateFaultException e) { System.out.println("Failed adding '" + title + "' with ISBN '" + isbn + "' - matches existing title '" + e.getFaultMessage().getBook().getTitle() + '\''); }          // создание экземпляра функции обратного вызова CallbackHandler cb = new CallbackHandler();          // асинхронное получение всех книг заданного типа GetBooksByType gbbt = new GetBooksByType(); gbbt.setType("scifi"); stub.startgetBooksByType(gbbt, cb); long start = System.currentTimeMillis(); synchronized (cb) { while (!cb.m_done) { try { cb.wait(100); } catch (Exception e) {} } } System.out.println("Asynchronous operation took " + (System.currentTimeMillis()-start) + " millis"); if (cb.m_response != null) { BookInformation[] books = cb.m_response.getGetBooksByTypeReturn(); ... 

В Листинге 5 используется формирование кода с параметром WSDL2Java -u, предназначенным специально для ADB. При использовании этого параметра для каждого класса модели данных и сообщения формируется отдельный исходный файл Java; если вы не используете этот параметр, ADB сформирует код таким образом, что все эти классы будут статическими внутренними классами создаваемой заглушки. Работать с отдельными классами значительно легче, поэтому, если вы используете ADB, указывайте параметр -u.

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

Распакованный ADB

Часто Web-сервисы разрабатываются на основе существующих интерфейсов API в виде вызова методов, и в этом случае может быть очень полезно встроить существующий API в Web-сервис. Это легко сделать; операции, определенные для сервиса (формально для portType, если быть более точным), по существу эквивалентны вызову методов из определения интерфейса. Единственное значимое отличие состоит в том, что входные и выходные данные сервиса представляют собой сообщения XML, вместо входных и возвращаемых значений вызова. Поэтому для того, чтобы встроить существующий API в описание Web-сервиса, вам достаточно определить, как представлять параметры вызова и возвращаемые значения в виде структуры сообщений XML.

К счастью, Microsoft® давно уже установила конвенцию в этой области, что позволяет нам не изобретать велосипед. Это соглашение называется wrapped document/literal (упакованным документально-литеральным) и является представлением, используемым .NET по умолчанию при объявлении вызова метода операцией Web-сервиса. По существу "упакованный" (wrapped) подход говорит о том, что каждое входящее сообщение является элементом XML, который состоит только из последовательности дочерних элементов, а каждое исходящее сообщение является элементом XML с одним дочерним элементом. В реализации Microsoft есть еще некоторые технические детали, которые на самом деле не особо важны, если вам не требуется полная совместимость с .NET, но мы их опустим; скажем только, что сообщения, использованные в примере с библиотекой (фрагмент представлен в Листинге 4) следует этому подходу.

WSDL2Java поддерживает распаковывание таких документально-литеральных сервисов в рамках формирования кода ADB. При использовании распаковки с соответствующим определением сервиса WSDL, созданная клиентская заглушка (а также серверный скелет) получается значительно более простой и понятной. В Листинге 6 показан код клиентского приложения, эквивалентный приведенному в Листинге 5, но с переданным WSDL2Java параметром uw для создания распакованного интерфейса. Классы сообщений, на порядок увеличивающие сложность в Листинге 5, большей частью исключаются в Листинге 6 (за исключением класса GetTypes), и теперь методы сервиса принимают параметры и возвращают результаты напрямую, а не встроенными в классы сообщений. В процессе работы ADB по-прежнему создает классы сообщений и использует их в сформированном коде, но, как правило, вы можете игнорировать эти классы и работать напрямую с данными.

Листинг 6. Распакованный клиентский код ADB
 // создание клиентской заглушки AdbUnwrapLibraryStub stub = new AdbUnwrapLibraryStub(target);          // получение книги напрямую String isbn = "0061020052"; BookInformation book = stub.getBook(isbn); if (book == null) { System.out.println("No book found with ISBN '" + isbn + '\''); } else { System.out.println("Retrieved '" + book.getTitle() + '\''); }          // получение списка определенных типов TypeInformation[] types = stub.getTypes(new GetTypes()); System.out.println("Retrieved " + types.length + " types:"); for (int i = 0; i < types.length; i++) { System.out.println(" '" + types[i].getName() + "' with " + types[i].getCount() + " books"); }          // добавление новой книги String title = "The Dragon Never Sleeps"; isbn = "0445203498"; try { stub.addBook("scifi", isbn, new String[] { "Cook, Glen" }, title); System.out.println("Added '" + title + '\''); title = "This Should Not Work"; stub.addBook("xml", isbn, new String[] { "Nobody, Ima" }, title); System.out.println("Added duplicate book - should not happen!"); } catch (AddDuplicateFaultException e) { System.out.println("Failed adding '" + title + "' with ISBN '" + isbn + "' - matches existing title '" + e.getFaultMessage().getBook().getTitle() + '\''); }          // создание экземпляра функции обратного вызова BooksByTypeCallback cb = new BooksByTypeCallback();          // асинхронное получение всех книг заданного типа stub.startgetBooksByType("scifi", cb); long start = System.currentTimeMillis(); synchronized (cb) { while (!cb.m_done) { try { cb.wait(100L); } catch (Exception e) {} } } System.out.println("Asynchronous operation took " + (System.currentTimeMillis()-start) + " millis"); if (cb.m_books != null) { BookInformation[] books = cb.m_books; ... 

 
Обновление Axis2 1.3
Когда эта статья готовилась к публикации, была практически готова к выходу версия Axis2 1.3. Проблемы распаковки ADB, описанные в этом разделе, были исправлены, поэтому если вы можете начать работать с кодом Axis2 1.3, работа с ADB покажется вам более приятной, чем с ранними версиями Axis2.
 
Основная проблема поддержки распаковки в ADB состоит в том, что она еще не до конца стабильна. Код, приведенный в Листинге 6, подходит для работы с Axis2 1.2 и содержит несколько крупных усовершенствований по сравнению с кодом, который использовался для распаковки ADB в Axis2 1.1.1. Однако для инструмента WSDL2Java необходимо, чтобы была изменена структура документа WSDL, используемого с другими примерами, - нужно переместить встроенную в код схему классов данных в отдельный документ схемы. Что еще более важно, код, приведенный в Листинге 6, не будет работать; последняя часть кода, использующая асинхронную передачу данных, выдаст ошибку вызова класса из-за ошибки в сформированном ADB клиентском коде заглушки.

К моменту выхода версии, следующей за версией Axis2 1.2, проблемы распаковки ADB должны быть большей частью устранены. Однако ADB - это не единственная среда связывания данных для Axis2, поддерживающая распаковку. В JiBX также реализована поддержка распаковки, и со времени выхода Axis2 1.1.1 версия JiBX стабильна. Увидеть клиентский код JiBX вы сможете чуть ниже в этой статье, после того, как мы рассмотрим остальные варианты связывания данных Axis2.

XMLBeans

XMLBeans - это общая среда обработки XML, в состав которой входит слой связывания данных. Она создавалась как проект BEA Systems, а впоследствии была передана организации Apache Foundation. XMLBeans была первой формой связывания данных, поддерживаемой Axis2, и продолжает быть наиболее популярным вариантом работы с Axis2, особенно со сложными определениями схем.

В Листинге 7 показаны наиболее интересные фрагменты клиентского кода XMLBeans для нашего примера. Так же, как и в базовом коде ADB (без распаковки) для ввода и вывода каждой операции создается отдельный класс. Однако XMLBeans отличается от ADB тем, что в ней добавляется класс для документа, который содержит класс ввода или вывода (например, GetBookDocument в дополнение к классу GetBook). Совокупный эффект при использовании XMLBeans по сравнению с ADB состоит в добавлении уровня создания объектов. В Axis2 нет поддержки распаковки XMLBeans, поэтому и нет способа избежать этого дополнительного уровня объектов. В результате классы, сформированные XMLBeans, получаются более сложными для использования, чем их эквиваленты в других средах связывания данных.

Listing 7. XMLBeans client code
 // создание клиентской заглушки XmlbeansLibraryStub stub = new XmlbeansLibraryStub(target);          // получение книги напрямую String isbn = "0061020052"; GetBookDocument gbd = GetBookDocument.Factory.newInstance(); GetBookDocument.GetBook gb = gbd.addNewGetBook(); gb.setIsbn(isbn); gbd.setGetBook(gb); GetBookResponseDocument gbrd = stub.getBook(gbd); BookInformation book = gbrd.getGetBookResponse().getGetBookReturn(); if (book == null) { System.out.println("No book found with ISBN '" + isbn + '\''); } else { System.out.println("Retrieved '" + book.getTitle() + '\''); }          // получение списка определенных типов GetTypesDocument gtd = GetTypesDocument.Factory.newInstance(); gtd.addNewGetTypes(); GetTypesResponseDocument gtrd = stub.getTypes(gtd); TypeInformation[] types = gtrd.getGetTypesResponse().getGetTypesReturnArray(); System.out.println("Retrieved " + types.length + " types:"); for (int i = 0; i < types.length; i++) { System.out.println(" '" + types[i].getName() + "' with " + types[i].getCount() + " books"); }          // добавление новой книги String title = "The Dragon Never Sleeps"; isbn = "0445203498"; try { AddBookDocument abd = AddBookDocument.Factory.newInstance(); AddBookDocument.AddBook ab = abd.addNewAddBook(); ab.setAuthorArray(new String[] { "Cook, Glen" }); ab.setIsbn(isbn); ab.setTitle(title); ab.setType("scifi"); stub.addBook(abd); System.out.println("Added '" + title + '\''); title = "This Should Not Work"; ab.setTitle(title); stub.addBook(abd); System.out.println("Added duplicate book - should not happen!"); } catch (AddDuplicateFaultException e) { System.out.println("Failed adding '" + title + "' with ISBN '" + isbn + "' - matches existing title '" + e.getFaultMessage().getAddDuplicate().getBook().getTitle() + '\''); }          // создание экземпляра функции обратного вызова BooksByTypeCallback cb = new BooksByTypeCallback();          // асинхронное получение всех книг заданного типа GetBooksByTypeDocument gbtd = GetBooksByTypeDocument.Factory.newInstance(); gbtd.addNewGetBooksByType().setType("scifi"); stub.startgetBooksByType(gbtd, cb); long start = System.currentTimeMillis(); synchronized (cb) { while (!cb.m_done) { try { cb.wait(100L); } catch (Exception e) {} } } System.out.println("Asynchronous operation took " + (System.currentTimeMillis()-start) + " millis"); if (cb.m_response != null) { BookInformation[] books = cb.m_response.getGetBooksByTypeResponse().getGetBooksByTypeReturnArray(); ... 

 
Изменения версии Axis2 1.3
Когда эта статья готовилась к публикации, была практически готова к выходу версия Axis2 1.3. Проблема обработки ошибок XMLBeans была исправлена в версии Axis2 1.3. Код примера для этой статьи для версии 1.3 можно найти в разделе Материалы для загрузки.
 
Клиентский код, приведенный в Листинге 7, и соответствующий серверный код, выполняются без ошибок в Axis2 1.1.1, однако из-за проблемы в формировании кода обработки ошибок для XMLBeans в версии 1.2, происходит сбой в той точке, где ожидается обработка исключения при добавлении дубликата идентификатора книги. Эта проблема должна быть исправлена в следующей версии Axis2.

Хотя в XMLBeans заявлена поддержка схемы XML на 100%, точность этого заявления зависит от трактовки. Верно, что почти для всех конструкций схемы XMLBeans формирует набор Java-классов, которые могут быть использованы для чтения и записи документов, соответствующих схеме. Однако, в отличие от других сред связывания данных, описанных в этой статье, XMLBeans по умолчанию практически ничего не делает для обеспечения соблюдения схемы. Например, если вы закомментируете строку, которая устанавливает название добавляемой книги в коде Листинге 7, XMLBeans будет успешно считывать и сохранять документы без обязательного элемента , которые поэтому не будут соответствовать схеме. В листинге 8 показано это изменение кода вместе с XML, отправленным на сервер для добавления запроса и XML, который был получен с сервера при запросе книг. В случае ответа на запрос в документе содержится элемент <title>, но используется атрибут <CODE>xsi:nil="true"</CODE>, не разрешенный схемой, и документ снова оформлен не по правилам.<br><br> <B>Листинг 8. Неверный документ и код XMLBeans</B><BR> <TABLE class=borderall cellSpacing=0 cellPadding=2 width="100%" bgColor=#d8d8d8> <TBODY> <TR> <TD><PRE> AddBookDocument abd = AddBookDocument.Factory.newInstance(); AddBookDocument.AddBook ab = abd.addNewAddBook(); ab.addAuthor("Cook, Glen"); ab.setIsbn(isbn); ab.setType("scifi"); // ab.setTitle(title); System.out.println("Validate returned " + abd.validate()); stub.addBook(abd); ... <addBook xmlns="http://ws.sosnoski.com/library/wsdl"> <type>scifi</type> <isbn>0445203498</isbn> <author>Cook, Glen</author> </addBook> <getBooksByTypeResponse xmlns="http://ws.sosnoski.com/library/wsdl"> ... <getBooksByTypeReturn isbn="0445203498" type="scifi"> <author xmlns="http://ws.sosnoski.com/library/types">Cook, Glen</author> <title xmlns="http://ws.sosnoski.com/library/types" xmlns:xsi="http://www.w3.org/2001 /XMLSchema-instance" xsi:nil="true"/> </getBooksByTypeReturn> </getBooksByTypeResponse> </PRE></TD></TR></TBODY></TABLE><BR> Это простой пример опущенного определения обязательного значения. Для более сложных схем интерфейс API, сформированный XMLBeans, может скрывать более серьезные ошибки. В ходе недавних дискуссий в списке рассылки XMLBeans рассматривался случай, в котором, чтобы сформировать правильный вывод, значения было необходимо добавлять в два различных списка в разном порядке. Поэтому для работы с XMLBeans разработчик должен знать и схему, и то, как сформированный код соответствует схеме, чтобы обеспечить формирование кодом приложения корректных документов XML. Одно из основных преимуществ сред связывания данных обычно состоит в том, что такие подробности схемы скрываются от разработчиков, и XMLBeans определенно проигрывает на этом фронте.<br><br> Вы можете избежать проблем обработки и формирования не соответствующих правилам документов XML при работе с XMLBeans, вызывая метод <CODE>validate()</CODE>, включенный в сформированные классы. Если вы работаете с XMLBeans, вам следует как минимум проверять этим методом все документы на этапах тестирования и разработки. Однако проверка оказывает значительное негативное воздействие на производительность (а, как вы увидите из следующей статьи этой серии, XMLBeans и так работает достаточно медленно, даже без вызова <CODE>validate()</CODE> для каждого документа), поэтому в рабочих приложениях следует избегать издержек, связанных с проверкой. Кроме того, проверка ограничена в плане информативности результата. Чтобы выяснить причину провала проверки, вам придется запустить для ошибочного документа стандартный инструмент проверки схемы.<br><br> <H4>JiBX</H4> JiBX (также разработанная мной) представляет собой среду связывания данных, которая главным образом фокусируется на работе с существующими Java-классами, а не с кодом, сформированным на основе схемы. При использовании JiBX сначала нужно создать описание связывания, определяющее, как объекты Java будут преобразовываться в XML и обратно, после чего скомпилировать это связывание с помощью инструмента, дополняющего файлы класса данных новыми методами (в виде байт-кода), реализующими преобразование. После этого интерактивная среда JiBX использует эти добавленные методы для преобразования данных в XML и обратно.<br><br> Подход JiBX имеет свои сильные и слабые стороны. К плюсам можно отнести то, что JIBX позволяет работать напрямую с существующими классами в случаях, когда к существующему кода сервиса добавляются новые интерфейсы Web-сервиса. Для этой цели особенно полезен инструмент Jibx2Wsdl, поскольку он формирует все, что нужно для простого развертывания существующего кода как сервиса Axis2. Вы можете определить несколько связываний для одного класса, чтобы одновременно работать с различными версиями документов XML, используя одну модель данных. Модифицировав связывание, вы можете даже сохранить представление XML при изменении классов данных.<br><br> В Листинге 9 показаны наиболее интересные фрагменты клиентского кода JiBX, использующего классы, соответствующие элементам сообщений. Этот код похож на его эквивалент для ADB, приведенный в Листинге 5, поэтому я не буду вдаваться в детали. Единственное заметное отличие вызвано тем, что и классы данных, и классы сообщений находятся под управлением пользователя, и поэтому при использовании JiBX очень просто добавлять классам вспомогательные конструкторы (как в случае с <CODE>AddBookRequest</CODE>) и другие дополнительные методы.<br><br> <B>Листинг 9. Клиенский код JIBX</B><BR> <TABLE class=borderall cellSpacing=0 cellPadding=2 width="100%" bgColor=#d8d8d8> <TBODY> <TR> <TD><PRE> // создание экземпляра сервера JibxLibraryStub stub = new JibxLibraryStub(target); // получение директории книги String isbn = "0061020052"; GetBookResponse bresp = stub.getBook(new GetBookRequest(isbn)); Book book = bresp.getBook(); if (book == null) { System.out.println("No book found with ISBN '" + isbn + '\''); } else { System.out.println("Retrieved '" + book.getTitle() + '\''); } isbn = "9999999999"; bresp = stub.getBook(new GetBookRequest(isbn)); book = bresp.getBook(); if (book == null) { System.out.println("No book found with ISBN '" + isbn + '\''); } else { System.out.println("Retrieved '" + book.getTitle() + '\''); } // получение списка определенных типов GetTypesResponse tresp = stub.getTypes(new GetTypesRequest()); Type[] types = tresp.getTypes(); System.out.println("Retrieved " + types.length + " types:"); for (int i = 0; i < types.length; i++) { System.out.println(" '" + types[i].getName() + "' with " + types[i].getCount() + " books"); } // добавление новой книги String title = "The Dragon Never Sleeps"; isbn = "0445203498"; try { AddBookRequest abr = new AddBookRequest("scifi", isbn, title, new String[] { "Cook, Glen" }); stub.addBook(abr); System.out.println("Added '" + title + '\''); title = "This Should Not Work"; abr = new AddBookRequest("scifi", isbn, title, new String[] { "Nobody, Ima" }); System.out.println("Added duplicate book - should not happen!"); } catch (AddDuplicateFaultException e) { System.out.println("Failed adding '" + title + "' with ISBN '" + isbn + "' - matches existing title '" + e.getFaultMessage().getBook().getTitle() + '\''); } // создание экземпляра обратного вызова BooksByTypeCallback cb = new BooksByTypeCallback(); // асинхронное получения всех книг заданного типа stub.startgetBooksByType(new GetBooksByTypeRequest("scifi"), cb); long start = System.currentTimeMillis(); synchronized (cb) { while (!cb.m_done) { try { cb.wait(100); } catch (Exception e) {} } } System.out.println("Asynchronous operation took " + (System.currentTimeMillis()-start) + " millis"); if (cb.m_response != null) { Book[] books = cb.m_response.getBooks(); </PRE></TD></TR></TBODY></TABLE><BR> В листинге 10 показан эквивалентный код JiBX с распаковкой. Как и в случае с кодом ADB с распаковкой, распакованную форму вызовов сервиса гораздо проще понимать и использовать, чем прямую. Единственная значительная разница между версиями JiBX и ADB состоит в том, что в случае JiBX вам не нужно создавать объект, когда значение не передается, как это было для вызова <CODE>getTypes()</CODE> в ADB. Кроме того, поддержка распаковки в JiBX также более стабильна, чем в версии ADB, поскольку она полностью поддерживается начиная с версии Axis2 1.1.1.<br><br> <B>Листинг 10. Клиентский код JIBX с распаковкой</B><BR> <TABLE class=borderall cellSpacing=0 cellPadding=2 width="100%" bgColor=#d8d8d8> <TBODY> <TR> <TD><PRE> // создание экземпляра сервера JibxUnwrapLibraryStub stub = new JibxUnwrapLibraryStub(target); // получение директории книги String isbn = "0061020052"; Book book = stub.getBook(isbn); if (book == null) { System.out.println("No book found with ISBN '" + isbn + '\''); } else { System.out.println("Retrieved '" + book.getTitle() + '\''); } // получение списка определенных типов Type[] types = stub.getTypes(); System.out.println("Retrieved " + types.length + " types:"); for (int i = 0; i < types.length; i++) { System.out.println(" '" + types[i].getName() + "' with " + types[i].getCount() + " books"); } // добавление новой книги String title = "The Dragon Never Sleeps"; isbn = "0445203498"; try { stub.addBook("scifi", isbn, new String[] { "Cook, Glen" }, title); System.out.println("Added '" + title + '\''); title = "This Should Not Work"; stub.addBook("xml", isbn, new String[] { "Nobody, Ima" }, title); System.out.println("Added duplicate book - should not happen!"); } catch (AddDuplicateFaultException e) { System.out.println("Failed adding '" + title + "' with ISBN '" + isbn + "' - matches existing title '" + e.getFaultMessage().getBook().getTitle() + '\''); } // создание экземпляра обратного вызова BooksByTypeCallback cb = new BooksByTypeCallback(); // асинхронное получения всех книг заданного типа stub.startgetBooksByType("scifi", cb); long start = System.currentTimeMillis(); synchronized (cb) { while (!cb.m_done) { try { cb.wait(100L); } catch (Exception e) {} } } System.out.println("Asynchronous operation took " + (System.currentTimeMillis()-start) + " millis"); if (cb.m_books != null) { Book[] books = cb.m_books; </PRE></TD></TR></TBODY></TABLE><BR> Поддержка распаковки в JiBX также отличается от ADB с точки зрения используемых классов. При использовании распаковки в ADB классы для всех элементов сообщений по-прежнему создаются и используются незаметно для пользователя. В JiBX при использовании прямой формы, как в Листинге 9; , необходимо определить классы для всех элементов сообщений; при работе с распакованной формой нужно определять и включать в определение связывания только классы, передаваемые в виде значений. В любом случае перед запуском инструмента Axis2 WSDL2Java необходимо создать определение связывания JiBX и передать его в параметре командной строки <CODE>-Ebindingfile</CODE>.<br><br> Самым большим недостатком подхода JiBX к связыванию, по меньшей мере, в отношении Web-сервисов, вероятно, является то, что на сегодняшний день в JiBX слабо реализована поддержка работы с определениями схемы XML. И даже эта слабая поддержка работы со схемой, в виде инструмента Xsd2Jibx, не интегрирована в инструмент формирования кода Axis2 WSDL2Java. Это означает, что перед запуском WSDL2Java для формирования кода связи Axis2 вам нужно создать определение связывания и классов данных Java. Шаг модификации байт-кода, необходимый в JiBX, также может вызывать проблемы в некоторых средах, поскольку его, в общем случае, необходимо выполнять во время сборки приложения; кроме того, он приводит к появлению кода в классах, не имеющих исходного кода.<br><br> Связывание данных посредством JiBX имеет ряд уникальных преимуществ, которые обсуждались в начале этого раздела. В отношении использования Axis2 JiBX также предоставляет преимущество над другими средами, которое заключается в том, что он поддерживается вместе с исправлениями, которые могут быть добавлены в Axis2 для исправления проблем, найденных после выхода версии. Единственным способом получения исправлений в других средах является переход на еженощную сборку версий Axis2, что зачастую может вызвать другие проблемы. В будущем ожидается реализация в JiBX эффективного формирования кода и связывания на основании схемы. Когда это будет сделано, JiBX обещает стать отличной универсальной альтернативой связыванию данных для Axis2. До этого времени она скорее подходит для работы с существующим кодом Java, где отлично работает инструмент Jibx2Wsdl.<br><br> <H3>Резюме</H3> На сегодняшний день в Axis2 реализована полная поддержка трех различных сред связывания данных:<br><br> <UL> <LI><B>ADB</B> разработан специально для Axis2 и может быть использован только в среде Axis2. По состоянию к моменту выхода версии Axis2 1.3 в ней реализована хорошая поддержка формирования кода на основании схемы - -, которая продолжает совершенствоваться. В ней также поддерживаются удобные распакованные методы сервиса и автоматическая обработка вложений, что делает ее оптимальным выбором при работе с существующими WSDL-определениями сервисов. <LI><B>XMLBeans</B> предлагает более полную поддержку структур моделирования схемы в сформированном коде Java. Однако она создает более сложную модель для заданной схемы и не поддерживает распакованных методов сервисов, упрощающих интерфейс. По умолчанию она не выполняет контроля соблюдения даже базовых правил структуры схемы ни во входящих, ни в исходящих документах XML, что повышает вероятность случайного создания или приема на обработку документов XML с ошибками. <LI><B>JiBX</B> - это единственный вариант, поддерживающий работу с существующими Java-классами. Кроме того, в ней реализована отличная поддержка распакованных методов сервиса. Однако поддержка JiBX, интегрированная в Axis2, не обеспечивает формирование кода на основании схемы, и даже формирование кода, реализованное отдельно в инструменте JiBX, на сегодняшний день предлагает лишь ограниченную поддержку схем. </LI></UL> То, что в Axis2 имеется возможность выбора сред связывания данных, замечательно, поскольку нет единого варианта, оптимально подходящего всем требованиям. В будущем в Axis2 будет также реализована полная поддержка JAXB 2.0, о котором я расскажу в рамках этой серии, в статье о JAX-WS 2.0. Знание сильных и слабых сторон каждой из сред может помочь вам сделать оптимальный выбор, соответствующий вашим потребностям, и предупредит о возможных проблемах до того, как вы встретитесь с ними в работе. В следующей статье этой серии вы узнаете о еще одном аспекте сравнения сред связывания данных в Axis2: производительности.<br><br> <br><br><center><script type="text/javascript"> <!-- var _acic={dataProvider:10};(function(){var e=document.createElement("script");e.type="text/javascript";e.async=true;e.src="https://www.acint.net/aci.js";var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t)})() //--> </script></center><!--furia--><p> </p><img src="http://team.furia.ru/images/marker.gif"> <a href=/fast_xml_48_team.html>pureXML в DB2 9: Каким способом запрашивать XML-данные? (исходники).</a><br><img src="http://team.furia.ru/images/marker.gif"> <a href=/fast_xml_50_team.html>XML в Oracle - это очень просто.</a><br><img src="http://team.furia.ru/images/marker.gif"> <a href=/fast_xml_53_team.html>Программирование на XML для DB2: Часть 1. Понимание модели данных XML (исходники).</a><br><img src="http://team.furia.ru/images/marker.gif"> <a href=/fast_xml_56_team.html>Освоение Ajax: Часть 10. Использование JSON для передачи данных (исходники).</a><br><img src="http://team.furia.ru/images/marker.gif"> <a href=/fast_xml_59_team.html>Лицензирование продуктов ABBYY.</a><br><br><br> <!--furia--><a href=/><b>Главная</b></a> » <span style="background-color:#07868b; color:#ffffff;"><b> Xml </b></span><br><br> </td> </tr> </tbody> </table> <table width="100%" cellpadding="0" cellspacing="0"> <tbody> <tr> <td class="connoisseur" width="100%"> <div id="centrifugal"> © 2024 <a href="/">Team.Furia.Ru</a>.<br> Частичное копирование материалов разрешено.</div> <div id="workshop"> <div class="guild"><script type="text/javascript"><!-- document.write("<a href='http://www.liveinternet.ru/click' "+ "target=_blank><img src='//counter.yadro.ru/hit?t14.12;r"+ escape(document.referrer)+((typeof(screen)=="undefined")?"": ";s"+screen.width+"*"+screen.height+"*"+(screen.colorDepth? screen.colorDepth:screen.pixelDepth))+";u"+escape(document.URL)+ ";"+Math.random()+ "' alt='' title='LiveInternet: показано число просмотров за 24"+ " часа, посетителей за 24 часа и за сегодня' "+ "border='0' width='88' height='31'><\/a>") //--></script></div> </div> </td> </tr> </tbody> </table> </body> </html>