Журнал LinuxFormat - перейти на главную

LXF95:Java EE

Материал из Linuxformat
Перейти к: навигация, поиск

Содержание

А я? (кс)

ЧАСТЬ 6 Заинтригованы ребусом в заголовке? То ли еще будет, когда прочитаете статью — Александр Бабаев большой мастер загадывать загадки, особенно если речь заходит о Web 2.0.

Когда я начинал писать эту статью в первый раз, я думал, что все достаточно просто. Сел-написал-отдал. Но сразу после этого пришлось переделывать одно приложение, которое изменило понимание и сложности, и самого Аякса. Полностью. В чем же сложность? В несоответствии. С одной стороны, Аякс позволяет приблизить логику работы интернет-приложений к обычной, «десктопной». С другой стороны — все браузеры таки разные. И это последнее «таки» местами убивает наповал. Вы думаете, что браузеров три? IE/Firefox/Opera? Хех. Для правильного приложения их нужно протестировать десяток. IE 5.5/6/7, Firefox 1.5/2, Opera 8/9, Safari 1.3/2/ (а теперь и 3). И у каждого свой нрав.

Ладно, что же такое Аякс?

AJAX — технология асинхронного доступа к серверу с использованием JavaScript и XML. Представим себе автомобиль. Чтобы заправить его - едем на заправку, заправляем, уезжаем. Чтобы поменять фару — едем, снимаем, ставим, уезжаем. Вот это — AJAX. А если бы Аякса не было, то для каждой такой операции нужно было бы отдавать машину в сервис, а из сервиса возвращалась бы другая, хоть и максимально точная копия вашей, с обновлениями (бак заправлен). Асинхронность — это вообще что-то вроде дозаправки в воздухе. То есть обновление данных без «отрыва от просмотра страницы». Давайте подробнее рассмотрим компоненты этой технологии:

A

Асинхронность реализуется разными способами. XMLHttpRequest, фреймы, Flash. Есть и другие (те же апплеты), но они распространены меньше. Второй способ — самый «простой» для человека, знакомого с HTML. Создается ма-а-аленький iframe (одна точка) и помещается куда-нибудь далеко, чтобы не было видно. Потом при необходимости подгрузить что-либо, запрос выполняется как обычно, но с целевым фреймом — тем самым, маленьким. Во фрейм загружается страничка, на которой обычно есть скрипт, который выполняется и делает свое дело (обновляет основной фрейм). Flash — это примерно то же самое, только вместо маленького скрытого фрейма используется маленькая «флэшка».

Впрочем, обычно оба эти способа используются только в случае, если не подходит «основной» — XMLHttpRequest. Это название объекта в JavaScript, который может выполнять асинхронные (и синхронные) HTTP-запросы. Например:

var xmlHttpRequestValue = new XMLHttpRequest();
xmlHttpRequestValue.open(“POST”,/ajax.jsp?action=update”, true);
xmlHttpRequestValue.onreadystatechange = processXmlHttpResponse;
xmlHttpRequestValue.send(“Информация для пересылки серверу”);

Все, запрос ушел. Сразу предупрежу, что это не рабочий код, только иллюстрация. Обратите внимание на третью строчку. Выделенное жирным — это имя функции, которая будет обрабатывать события, генерируемые во время передачи запроса, такие, как «начат прием данных», «закончен прием заголовка», «закончен прием данных». Если мы хотим что-либо предпринять по окончании приема данных, эта функция будет выглядеть как-то так:

function processXmlHttpResponse() {
if (xmlHttpRequestValue.readyState == 4) {
…сделать «что-либо»…
}
}

Число «4» как раз обозначает, что обработка завершена. Функция processXmlHttpResponse() может вызываться много раз (даже точно не определено, сколько), но как только readyState станет равным 4, мы это «поймаем» и обработаем результат.

J

JavaScript — объектный язык. То есть создать класс, как в Java, в нем нельзя. А вот новый объект — запросто. И сформировать структуру объекта (прописать методы, поля) «на лету» тоже можно.

Чаще всего JavaScript используется как простой способ динамически изменять структуру документа. Например, чтобы поменять цвет параграфа, можно выполнить такой код:

document.getElementById(“paragraph”).style.color = “red”;

Параграф после этого покраснеет. Чтобы понимать, как добраться до отдельных параграфов, нужно изучить DOM (Document Object Model).

X

XML, который попал в AJAX последней буквой, используется (как уже упоминалось) далеко не всегда, но часто. Благо, в JavaScript есть возможности по достаточно простому преобразованию чего угодно в XML, да и в других языках (не только в Java) библиотеки по работе с этим форматом достаточно хорошо развиты.

Помощники

Из-за того, что реализации XMLHttpRequest различаются (в паре крупных деталей и, что хуже, в паре десятков мелочей), самому писать кросс-браузерную обработку достаточно тяжело и долго. Поэтому давайте посмотрим на библиотеки, в состав которых входит «AJAX-подсистема» и которые сделаны либо специально для Java-северной составляющей, либо поддерживают ее.

  • DOJO
    Огромная библиотека, которая много чего умеет. Если смотреть только на коммуникативные возможности, то тут тоже все в порядке: можно использовать XMLXttpRequest, фрейм, Flash. Первый вариант предлагается по умолчанию. Чуть ниже будет пример работы с DOJO, а сейчас просто отметим, что с учетом достаточно приличной документации, логичной организации и большого количества модулей (в том числе и для использования разных методов коммуникации), библиотеку можно назвать хорошей.
    DWR — Direct Web Remoting
    Сделана специально для Java. Достоинство состоит в том, что DWR умеет «публиковать» классы Java в JavaScript. То есть вы пишете класс в Java, настраиваете DWR (при помощи XML-дескриптора), и после запуска сервлета (обработкой запросов от DWR занимается отдельный сервлет) в JavaScript «появляются» методы этого класса, которые можно вызывать, так же, как если бы это были обычные функции JavaScript. Способ удобен, но навязывает определенный способ коммуникации. Подходит, когда не нужно контролировать каждый байт передаваемых данных и если с JavaScript вы знакомы не очень хорошо (хотя все равно его нужно знать, так как кроме коммуникации есть много чего еще).
    GWT — Google Web Toolkit
    В свое время эта библиотека наделала много шуму. Все дело в принципе ее работы. В общем и целом, если на DWR мы пишем на Java коммуникацию, то на GWT — все приложение. Затем GWT компилирует его в клиентскую часть (HTML + JavaScript) и серверную часть (Java). То есть клиент получается автоматически (не целиком, но близко к тому). GWT берет на себя все заботы и по взаимодействию клиента с сервером, и по созданию интерфейса (этот процесс несколько напоминает Swing), и многое другое. В принципе, достаточно хорошее решение, естественно со своими нюансами. Мы еще посмотрим на него поближе… к концу статьи.

Адресная книга с AJAX

Настало время потренироваться. Давайте возьмем нашу адресную книгу и сделаем так, чтобы показывалась страничка с табличкой (списком телефонов), а по нажатии на ссылки вместо перехода на новые странички менялся сам список: удалялись и добавлялись строки и так далее. Использовать будем DOJO. Идеология от этого не пострадает, но станет немного проще, короче и понятнее.

HTML/JavaScript

Вот строка HTML-таблицы, из которой видно, как будет выводиться список телефонов.

<tr id=1232323” >
<td>1232323</td><td>Vasya Beanov</td><td><td></td><td><a
href=”#” on click=”remove(‘1232323’); return false;”>Remove</a> / <a
href=”#” on click=”startEdit(‘1232323’); return false;”>Edit</a></td>
</tr>

Обратите внимание на id="…". Это своеобразные метки, которые позволят при необходимости найти нужную строку. Теперь рассмотрим JavaScript-код, который выполняется при нажатии на «кнопку» Edit (как самую сложную).

function startEdit(aPhone) {
getCell(aPhone, 0).innerHTML =<input type=”text” value =”’ +
getCell(aPhone, 0).innerHTML + ‘” id =”phone_’ + aPhone + ‘”/>;
getCell(aPhone, 1).innerHTML =<input type=”text” value = “’ +
getCell(aPhone, 1).innerHTML + ‘” id = “name_’ + aPhone + ‘”/>;
getCell(aPhone, 2).innerHTML =<input type = “text” value =”’ +
getCell(aPhone, 2).innerHTML + ‘” id =”comment_’ + aPhone + ‘”/>;
getCell(aPhone, 3).innerHTML =<a href = “#” onclick = “submitEdit(\’’
+ aPhone + ‘\’); return false;>Save changes</a>;
}

Простым русским языком этом можно выразить так: ячейки с текстом заменяются на ячейки с полями ввода.

После редактирования и нажатия на кнопку Save changes данные отсылаются на сервер, и в ячейках снова прописывается текст.

function submitEdit(aPhone) {
getCell(aPhone, 0).innerHTML = document.getElementById(‘phone_’ +
aPhone).value;
getCell(aPhone, 1).innerHTML = document.getElementById(‘name_’ +
aPhone).value;
getCell(aPhone, 2).innerHTML = document.getElementById(‘comment_’
+ aPhone).value;
getCell(aPhone, 3).innerHTML =<a href = “#” onclick = “startEdit(\’’ +
aPhone + ‘\’); return false;>Edit</a> <a href = “#” onclick = “remove(\’’
+ aPhone + ‘\’); return false;>Remove</a>;
sendAJAXRequest(“action=edit” +&old=+ aPhone +&phone=+ getCell(aPhone, 0).innerHTML +&name=+ getCell(aPhone, 1).innerHTML +&comment=+ getCell(aPhone, 2).innerHTML);
}

Теперь посмотрим, как отсылаются и принимаются данные. Для этого у нас есть показательная функция loadTable(), которая загружает табличку с сервера. Остальные функции только отправляют данные.

function loadTable() {
dojo.io.queueBind({
url :/ajax?action=loadTable”,
method : “get”,
load : function (aType, aData, aEvent) {
document.getElementById(“table”).innerHTML = aData;
},
preventCache : true
});
}

Обратите внимание на две вещи. Первая — использование dojo. queueBind — это местная реализация AJAX. Нам не нужно беспокоиться, что будет использоваться «внутри» (хотя контролировать внутренности тоже можно). Вторая — параметр load. Это функция, которая вызывается после загрузки данных с сервера (сами данные передаются в параметре aData). sendAJAXRequest выглядит абсолютно так же, только без параметра load.

Кстати, в queueBind’е сами параметры обрамлены фигурными скобками. Это как раз и есть объектность JavaScript. Тут создается объект с полями url, method, load и preventCache, и уже этот объект передается в качестве параметра функции.

Серверная часть

А теперь посмотрим, как это все обрабатывать на сервере. Логика его работы осталась той же: опять действия. Только вместо выдачи большого количества HTML-кода (через шаблоны или напрямую), в большинстве случаев не выдается ничего. Вот, например, код действия удаления:

String phone = aRequest.getParameter(“phone”);
_recordsBook.removeRecord(phone);

Только действие создания таблицы телефонов выдает код таблицы (но только ее, а не всю страничку). Остальные методы только получают данные и сохраняют их. Изменение таблицы происходит на клиенте при помощи JavaScript (как — можно посмотреть на код submitEdit выше или в полных исходных текстах на LXFDVD).

GWT

А что делать, если вы вообще ничего не понимаете в JavaScript? Не расстраиваться, скачать GWT (он немаленький, но и перспектива читать 1000 с лишним страниц талмуда под названием «JavaScript: The Definitive Guide» тоже не вдохновляет, ведь правда? К тому же архив можно найти и на нашем DVD), установить. А дальше?

Дальше процедура очень похожа на то, что позволяет делать, например, Ruby on Rails (или аналогичные каркасы — см. LXF95:Akelos):

  • Вы создаете некий код. Клиентский, серверный, шаблон странички. При необходимости также можно применять свои CSS-стили.
  • Затем создаете специальный XML-дескриптор. Это файл, который описывает, что нужно запускать, что будет сервером.
  • Запускаете компилятор GWT. Он читает клиентский код, создает HTML-странички (испoльзуя ваши шаблоны) и JavaScript-код, который будет связываться с сервером и выполнять запросы, которые были описаны ранее.
  • Запускаете сервер GWT-приложений.

При этом вы не пишете ни строчки на JavaScript. HTML и CSS — да, приходится, но опять же достаточно немного. Посмотрим подробнее?

Структура приложения GWT

Для начала стоит создать HTML-файл, который будет «заготовкой» для странички. Вот его код:

<html>
<head><meta name=’gwt:module’ content=’phoneBook.PhoneBook’></
head>
<body>
<script language=”javascript” type=text/javascript” src=”gwt.js”></
script>
<table>
<tr><td>Phone: </td><td id=”phoneCell”></td>
<td>Name: </td><td id=”nameCell”></td>
<td>Comment: </td><td id=”commentCell”></td>
</tr>
<tr><td colspan=”6” id=”buttonCell”></td></tr>
</table>
<div id=”main”></div>
</body>
</html>

Обратите внимание, что ни одной кнопки нет. И таблицы нет. Зато есть элементы, которые помечены id, в них-то и будут вставляться компоненты GWT, которые здесь называются виджетами.

Виджеты

Сами виджеты будут вставляться уже из «Java-кода». Почему в кавычках? Просто это код только пишется на Java, но потом компилируется в JavaScript и выполняется в браузере. Из-за этого есть множество ограничений. Например, нельзя использовать конструкции Java 5, такие как новые циклы foreach, аннотации, обобщенное программирование (generics) и другие. Нельзя использовать все классы подряд — только те, для которых в GWT есть «реализация» (для java. lang.* и java.util.* — есть, и это сильно упрощает дело). Код вставки виджетов выглядит примерно следующим образом:

Button button = new Button(“Add new record”);
RootPanel.get(“buttonCell”).add(button);

Первая строчка создает кнопку, вторая вставляет ее в элемент с id «buttonCell».

Список виджетов достаточно велик, и можно также создавать свои собственные. Например, для вывода таблицы контактов можно использовать FlexTable.

FlexTable table = new FlexTable();

Этот виджет умеет динамически изменяться, например, вот этак:

aTable.setText(aRow, aColumn, “Текст в ячейку”);

Этот код вставляет в ячейку таблицы текст. Можно также вставить туда и другой виджет, например, так:

aTable.setWidget(aRow, aColumn, new Button(“Edit”));

Для работы нужны не только виджеты, но и обработчики событий. Это тоже реализовано достаточно просто: через слушателей (см. LXF92), как в Swing. Вот как, например, добавляется обработчик нажатия на кнопку:

editButton.addClickListener(new ClickListener() {
public void onClick(Widget aWidget) {
код обработчика…
}
}

Есть, правда, и кардинальное отличие — в асинхронности. Для этого перейдем к сервисам.

Сервисы

Для обработки клиентских запросов пишутся так называемые сервисы. Чтобы собрать сервис, создается два интерфейса (phoneBook.client.PhoneBookService и phoneBook.client.PhoneBookServiceAsync) и реализация (phoneBook.server.PhoneBookServiceImpl). В сервис выносятся методы, которые сервер выполняет по запросу клиента (у нас это добавление, удаление, редактирование записей и выдача списка записей).

В «асинхронном» интерфейсе прописываются те же методы, что и в «обычном», но с одним дополнительным параметром AsyncCallback async. Это «Callback», обратный вызов. Когда делается асинхронный вызов, выполнение кода (работа с пользователем) продолжается. А когда работа асинхронного запроса завершается, вызывается этот обратный вызов, который сообщает клиенту: «Товарищ, задание партии выполнено, список пользователей доставлен».

Поэтому обработчики событий (например, кнопки удаления записи) выглядят примерно следующим образом (показан только метод onClick соответствующего обработчика):

// вызов сервиса, второй параметр – это и есть обратный вызов,
// то есть то, что вызывается после выполнения запроса
PhoneBookService.App.getInstance().
removeRecord(_phone, new AsyncCallback() {
public void onFailure(Throwable aThrowable) {
// ничего тут не будем делать
}
public void onSuccess(Object o) {
// удаляем строку из таблицы, соответствующую телефону
for (int i = 0; i < _table.getRowCount(); i++) {
if (_table.getText(i, 0).equals(_phone)) {
_table.removeRow(i);
break;
}
}
}
});

Компиляция и запуск

(thumbnail)
Итог терзаний — все та же адресная книга и консоль на заднем плане.

Ранее я говорил про дескриптор. Он очень прост и выглядит примерно так:

<module>
<inherits name=’com.google.gwt.user.User’/>
<entry-point class=’phoneBook.client.PhoneBook’/>
<servlet path=’/PhoneBookService’ class=’phoneBook.server.PhoneBookServiceImpl’/>
</module>

Чтобы скомпилировать GWT-код, нужно запустить класс com. google.gwt.dev.GWTCompiler с параметром «полное имя класса, который нужно скомпилировать». Также можно указать параметр -out «каталог, куда складывать результат».

Запуск специальной оболочки для отладки осуществляется при помощи класса com.google.gwt.dev.GWTShell, которому в качестве параметра передается путь к HTML-файлу запуска. Более подробные строки компиляции и запуска лучше посмотреть в примерах, которые поставляются вместе с самим GWT. А как выглядит Google Web Toolkit Development Shell, можно узнать из рисунка — «вон он, змей, в окне маячит…»

Ну, а все-таки, если делать самому?

Давайте поподробнее остановимся на некоторых подводных камнях технологии, связанных и с Java и с XMLHttpRequest’ом и с остальными ее компонентами.

Во-первых, Java (точнее, серверная сторона). Тут проблема одна, UTF-8. Почему-то некоторые браузеры не понимают UTF-8 в ответах. Safari, например. Есть достаточно простой обходной путь — выставлять не совсем правильный, но работающий тип (Content-Type) содержимого ответа (response), «text/plain; charset=utf-8».

Далее — клиентская часть. Тут подводных камней больше. Во-первых, количество соединений: на сайт браузер разрешает обычно не более двух. Поэтому «сделаем сразу 239 AJAX-запросов» не получится. Один-два в параллели, и все.

Во-вторых, выше упоминалось про callback, вызываемый, когда идет процесс загрузки ответа с сервера. Как и сколько раз он вызывается — четко не определено. То есть нельзя надеяться, что он вызовется с кодом события 4 ровно один раз.

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

Именно поэтому примеры приводятся с использованием сторонних библиотек. А если уж и нужно сделать «самому», то стоит воспользоваться опытом и посмотреть их исходные тексты. В этом-то и сила Open Source.

Вот и весь AJAX

В статье рассмотрен AJAX «по верхам». Тема безграничная, так как кроме технологии, использование AJAX меняет стиль и принципы работы web-приложений. Все то, как они работали до этого, должно быть переосмыслено на совершенно другом уровне. Дерзайте, и все получится. Клиенты будут довольны, а, следовательно, разработчики будут получать больше денег.

Персональные инструменты
купить
подписаться
Яндекс.Метрика