Ключевые слова:web, javascript, gui, java, (найти похожие документы)
From: Кирилл Сухов
Date: Mon, 16 Apr 2010 17:02:14 +0000 (UTC)
Subject: Создание современных web-приложений при помощи Google Web Toolkit
Материал предоставлен редакцией журнала Системный администратор.
Опубликовано в журнале "Системный администратор" N 1 2010
Любое интернет-приложение эпохи WEB2 - это прежде всего "богатый" и
функциональный пользовательский интерфейс, выполненный с использованием
технологии Ajax, а следовательно, JavaScript
Его создание - довольно непростая, требующая скрупулезности задача, на
решение которой тратишь немало времени - на отладку, обеспечение
кроссбраузерной совместимости, борьбу с капризами JavaScript и прочие
вещи, мало имеющие общего с разработкой и проектированием реализации
бизнес-логики. К счастью, уже создано немало инструментов, облегчающих
эту задачу. Фрэймворк Google Web Toolkit пусть и не единственная
предназначенная для этого среда, но она уже успела себя зарекомендовать
полнофункциональными, работоспособными RIA-приложениями.
Что это? Зачем?
На первый вопрос ответить легко - это фрэймворк-среда, набор средств и
API-интерфейсов для разработки веб-приложений. Её отличительная
особенность в том, что вся разработка и, что существенно, отладка
ведётся на Java с помощью привычной IDE (сейчас существуют плагины для
Eclipce и NetBeans) с последующей компиляцией готового приложения в
HTML/JavaScript. Собственно, реальность использования сред разработки
частично является ответом на второй вопрос. Создавать сложное
веб-приложение на строго типизированном ООП-языке, с возможностью
нормального проектирования в любимой IDE, "человеческой" отладкой,
Unit тестированием ещё недавно казалось недостижимой мечтой.
Google Web Toolkit во многом является воплощением этой мечты в жизнь.
Процесс отладки приложения здесь гораздо более лёгок и эффективен, так
как наиболее распространённые ошибки в JavaScript теперь всплывают во
время компиляции, а не выполнения, а такие ошибки, как несоответствие
типов или отсутствие необходимых методов, выявляются ещё на стадии
написания кода. Подсказки и автодополнение - нормальный функционал IDE,
хоть и не жизненно важный, но довольно существенно повышающий
производительность, а полноценный рефакторинг (который также теперь
доступен) в современных условиях просто необходим.
Отдельно следует упомянуть про ООП-разработку. Реализация этой
концепции в JavaScript вызывает много справедливых нареканий и является
причиной неоправданной сложности разработки. В среде GWT вы
программируете на Java, и никаких проблем такого рода не возникает.
Кроме того, при написании веб-приложений теперь можно использовать
такие привычные утилиты, как Jprofiler или Junit.
Как это работает?
Код пишется и отлаживается на Java с использованием типов данных из
пакетов java.lang и java.util, а также с новыми классами,
предоставляемыми GWT. Поддерживаются все внутренние типы Java (в том
числе и Object). Поддерживается работа с исключениями, в том числе и
определяемыми пользователем.
После отладки приложения при помощи GWT-компилятора создаётся
приложение, использующее традиционные веб-технологии -
HTML/JavaScript/XML/JSON, которое для GWT-приложения является аналогом
бинарного представления в Java. Но GWT это не совсем Java!
Прежде всего не поддерживается Reflection и динамическая загрузка
классов (что естественно, этот механизм просто невозможно перенести в
JavaScript). Не поддерживается сериализация. Не поддерживается
модификатор Strict Floating Point.(strictfp), предписывающий
"строгую" арифметику для чисел с плавающей точкой. Нет финализации
объекта перед сборкой мусора.
GWT-приложение как любое веб-приложение состоит из клиентской
(обрабатываемой в браузере) и серверной частей. Весь код, отвечающий за
клиентский функционал, в процессе компиляции переводится в
html/javascript и, следовательно, имеет ряд естественных ограничений.
Серверная часть приложения может вызываться клиентским кодом
посредством асинхронных RPC-запросов. При этом серверный код
выполняется отдельно от клиентского и не имеет каких-либо ограничений,
накладываемых компиляцией в JavaScript. Более того, нет ограничений в
выборе технологий реализации серверной части программы. Наряду с Java
это может быть PHP, Perl, Python и т.д.
Механизм отложенного связывания (deffered binding), выполняемого при
генерации JavaScript, обеспечивает решение проблем с кроссбраузерной
совместимостью и локализацией приложения. GWT компилирует различные
версии приложения под каждый браузер и локализацию. Во время запуска
клиентской части такого приложения в браузере определяется нужная
версия, она и поставляется пользователю.
Библиотеки GWT лицензированы под Apache License 2.0, что даёт полную
свободу использования среды как в открытых, так и в пропроетарных
приложениях.
Как установить?
На компьютере должна быть установлена Java SDK версии 1.5 или выше. Ещё
одно требование - наличие Apache Ant, java-утилиты для автоматизации
процесса сборки. Если её нет, просто скачайте из сайта проекта
(http://ant.apache.org) и распакуйте в любое удобное место, не забыв
прописать переменную окружения ANT_HOME и путь к Ant/bin в переменной
PATH. (Впрочем, при использовании Windows можно прибегнуть к сценарию
установки в составе дистрибутива.)
Сначала скачиваем дистрибутив с сайта code.google.com
(http://code.google.com/intl/ru/webtoolkit/download.html) и
распаковываем его в выбранную директорию. Собственно, на этом процесс
установки закончен. Осталось только прописать путь к этой папке в
системной переменной PATH.
Для проверки работоспособности GWK перейдём в папку /samples,
расположенную в корневой папке установленного фрэймворка. Это примеры
простейших приложений использования среды. Заходим (в консоли) в
/samples/Hello и выполняем команду:
ant hosted
Если всё правильно установлено, результат должен быть похож на рис. 1.
Этой командой мы запускаем приложение в так называемом размещённом
(hosted) режиме. В нём оно выполняется на виртуальной машине Java
(JVM). Этот режим предназначен для отладки и разработки.
Мы видим два окна встроенного браузера, в первом из них запущено само
приложение, во втором отображается отладочная информация, в том числе
данные Http-запросов.
Теперь откомпилируем наше приложение в HTML/Java Script. Для этого
вызовем команду:
ant build
запускающую GWT-компилятор. Результат его работы можно увидеть в папке
samples/Hello/war, раскрыв в браузере файл MailBoxes.html. Как видим
(см. рис. 2), всё прошло успешно. Правда, не впечатляет.
Чтобы увидеть настоящее GWT-приложение, заглянем в папку samples/Mail,
где хранятся демонстрационные примеры приложений, проведем аналогичные
действия. В результате получим уже нечто вполне приемлемое (см. рис. 3).
Первое приложение
Теперь попробуем создать собственное простенькое GWT-приложение. Для
этого воспользуемся утилитой webApp Creator, входящей в комплект GWT:
webAppCreator -out MailBoxes com.samag.MailBoxes
MailBoxes - это название нашего приложения (да-да, интерфейс к
почтовому серверу). После выполнения команды будет создано несколько
папок и файлов в папке MailBoxes/, которые составят скелет приложения и
обеспечат базовый "Hello GWT" функционал:
Created directory MailBoxes/src
Created directory MailBoxes/war
Created directory MailBoxes/war/WEB-INF
Created directory MailBoxes/war/WEB-INF/lib
Created directory MailBoxes/src/com/samag
Created directory MailBoxes/src/com/samag/client
Created directory MailBoxes/src/com/samag/server
Created file MailBoxes/src/com/samag/MailBoxes.gwt.xml
Created file MailBoxes/war/MailBoxes.html
Created file MailBoxes/war/MailBoxes.css
Created file MailBoxes/war/WEB-INF/web.xml
Created file MailBoxes/src/com/samag/client/MailBoxes.java
Created file MailBoxes/src/com/samag/client/GreetingService.java
Created file MailBoxes/src/com/samag/client/GreetingServiceAsync.java
Created file MailBoxes/src/com/samag/server/GreetingServiceImpl.java
Created file MailBoxes/build.xml
Created file MailBoxes/README.txt
Created file MailBoxes/.project
Created file MailBoxes/.classpath
Created file MailBoxes/MailBoxes.launch
Created file MailBoxes/war/WEB-INF/lib/gwt-servlet.jar
Скрипт webAppCreator создал несколько файлов в каталоге MyApplication/,
в том числе базовую функциональность "Hello, world" в классе. В
корневой папке приложения появился сценарий его сборки, для Ant -
build.xml, следовательно, наше приложение уже можно компилировать и
запускать. Воспользовавшись вышеописанными командами ant hosted и ant
build, мы получим простейшее рабочее AJAX (а как же!) приложение (см.
рис. 4). Как видите, оно состоит из поля ввода, куда следует поместить
имя, и кнопки "Отправить", после нажатия на которую появляется ответ
сервера с приветствием, учитывающим ваши данные.
Давайте теперь посмотрим, что у него внутри.
GWT-приложения оформлены в виде модулей, их структура подчиняется
правилам, сходным с организацией пакетов в Java-приложении. Она
определяется в xml-файле (в нашем случае MessageBox.gwt.xml).
Как уже говорилось, GWT-приложение состоит из двух основных частей -
клиентской и серверной. Клиентский код, как нетрудно догадаться,
содержится в MailBoxes/src/com/samag/client/. как и
MailBoxes/src/com/samag/server/, он расположен на одном уровне с
конфигурационным файлом.
В папке MailBoxes/war/ расположены все веб-ресурсы приложения - HTML,
CSS, JavaScript файлы. Там же расположена директория WEB-INF, в которой
содержатся метаданные и GWT JavaScript-библиотека среды исполнения (GWT
run-time JavaScript library).
Если мы заглянем в исходный код файла
MailBoxes/src/com/samag/client/MailBoxes.java (основного кода нашего
приложения), то обнаружим обычный Java-код. Вот кнопка Send:
//Создание кнопки
final Button sendButton = new Button("Send");
// Привязка её к таблице стилей
sendButton.addStyleName("sendButton");
// Добавление в панель приложения
RootPanel.get("sendButtonContainer").add(sendButton);
Я не собираюсь пересказывать документацию [2], но базовые понятия
изложить придется. Основной класс приложения (в нашем случае MailBoxes)
реализует интерфейс EntryPoint. Его метод onModuleLoad() вызывается в
тот момент, когда веб-страница с встроенным GW- модулем отображается в
браузере клиента. Все элементы визуального интерфейса (кнопки, поля
ввода, чекбоксы и т.д.) унаследованы от суперкласса Widget. Все эти
виджеты компонуются в рамках объекта класса Panel, который, разумеется,
сам является виджетом (его аналог в .Swing - Layout).
Компоновка в пространстве веб-страницы происходит в элементы контейнера
HTML (в данном случае sendButton Container- это id HTML-элемента <td>),
в который будет помещена кнопка. Далее добавляется обработчик:
class MyHandler implements ClickHandler, KeyUpHandler {
public void onClick(ClickEvent event) {
sendNameToServer();
}
...........................................
}
И связывается с кнопкой:
MyHandler handler = new MyHandler();
sendButton.addClickHandler(handler);
Мне кажется, что всё ясно. Про серверную часть пока речь не ведём, а
что касается компиляции в JavaScript/HTML, то сгенерированные
JavaScript объекты загружаются в контейнеры, обозначенные в шаблоне
HTML-страницы (MailBoxes/war/MailBoxes.html). Поскольку наше приложение
сложностью не отличается, то и шаблон довольно прост:
<table align="center">
<tr>
<td colspan="2" style="font-weight:bold;">Please enter your name:</td>
</tr>
<tr>
<td id="nameFieldContainer"></td>
<td id="sendButtonContainer"></td>
</tr>
</table>
Теперь попробуем ввести какой-нибудь новый функционал, например,
добавим кнопку Reset, очищающую поле ввода. Прежде всего чуть
преобразуем шаблон, добавив нужный контейнер:
<table align="center">
<tr>
<td colspan="3" style="font-weight:bold;">Please enter your name:</td>
</tr>
<tr>
<td id="nameFieldContainer"></td>
<td id="sendButtonContainer"></td>
<td id="resContainer"></td>
</tr>
</table>
Теперь в MailBoxes/src/com/samag/client/MailBoxes.java добавляем новый
виджет (в терминах данной среды кнопка и прочие функциональные элементы
графического интерфейса - это именно виджеты (widgets). После кода
создания кнопки Send добавляем строчку:
final Button sendButton = new Button("Send");
final Button resButton = new Button("Reset");
Добавим этот виджет на главную панель приложения:
RootPanel.get("sendButtonContainer").add(sendButton);
RootPanel.get("resContainer").add(resButton);
Теперь следует связать кнопку с внешним событием (в данном случае с
кликом по кнопке). Для этого в GWT существуют различные интерфейсы.
GWT предоставляет несколько интерфейсов Listener для улавливания
событий (щелчков мыши, нажатия клавиатуры, изменения содержаний поля
ввода и прочие, знакомые по Javascript). Один из них - ClickHandler -
отвечает за обработку клика мыши. Напишем его реализацию:
resButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
nameField.setText("");
sendButton.setFocus(true);
}
});
Готово. Компилируем приложение и наслаждаемся новым функционалом.
Всё это хорошо, но преимущества нового способа разработки совершенно не
очевидны. Не для того мы совершали столько телодвижений, чтобы учить
ещё один диалект Java и отлавливать сообщения об ошибках в консоли.
Сделаем следующий шаг - подключим к разработке современную IDE.
Работа в IDE Eclipce
Сначала скачаваем и установливаем плагин для IDE Eclipse
(http://code.google.com/intl/ru/eclipse). К слову сказать, если GWT
ещё не установлены, это будет сделано на данном этапе автоматически (а
заодно и SDK App Engine - средство разработки для платформы Google App
Engine).
Инсталляция плагина несколько отличается для разных версий IDE. Для
Eclipse 3.5 эта процедура выглядит следующим образом:
Заходим в Help -> Install New Software. В появившимся окне в поле Work
with вводим url: http://dl.google.com/eclipse/plugin/3.5, нажимаем
Add. В появившейся форме вводим название для источника обновлений
(например, Google Updute) и после подтверждения выбираем необходимые
компоненты (см. рис. 5). Поскольку GWT SDK мы уже установили, его можно
пропустить. Далее остаётся только пару раз нажать Next и принять
лицензионное соглашение.
Теперь импортируем наше приложение в IDE. Для этого нажимаем File ->
Import, в появившемся окне мастера выбираем General -> Existsing
Project into Workspace. В следующем окне в поле Select root directory
указываем путь до корня нашего приложения и жмём Finish. Приложение
должно появиться в левом окне Eclipse (см. рис. 6).
Для включения поддержки GWT щёлкаем на проекте правой кнопкой мыши. И в
контекстном меню выбираем Google -> Web Toolkit Setting. В появившемся
окошке помечаем чекбокс Use Google Web Toolkit. Если мы ставили GWT SDK
не вместе с плагином, то, нажав на ссылку Configure SDK, указываем
расположение фрэймворка.
Разработка приложения
Теперь можно приступить к действительно полезным вещам. В рамках
поставленной задачи нам нужно вывести список почтовых ящиков. С
возможностью добавления новых, удаления старых, а также
активации/деактивации акаунтов.
Список ящиков - это, очевидно, таблица. Чтобы выбрать подходящий объект
из арсенала GWT, отправляемся в галерею виджетов
(http://code.google.com/intl/ru/webtoolkit/doc/1.6/DevGuide.html),
представленную в документации фрэймворка, и выбираем наиболее
подходящий объект для нашей задачи. В данном случае это будет
Grid-реализация абстрактного класса HTMLTable.
Включаем его в наше приложение:
final Grid mailGrid = new Grid(1,5);
mailGrid.setTitle("mailboxes");
При этом вначале надо импортировать класс из соответствующего
GWT-пакета:
import com.google.gwt.user.client.ui.Grid;
В дальнейшем добавления почти любых новых элементов влечет за собой
импорт необходимых пакетов, я не буду на этом останавливаться, так как
их название подскажет IDE. Если лёгкий путь не для вас и разработка
происходит в vi/notepad, можно воспользоваться описанием применяемых
нами компонентов в документации.
Заполняем первый ряд таблицы (заголовки столбцов):
mailGrid.setText(0, 0, "ID");
mailGrid.setText(0, 1, "Name");
mailGrid.setText(0, 2, "Email");
mailGrid.setText(0, 3, "Activity");
Далее заполним таблицу значениями.
this.getUsers(mailGrid);
По-настоящему такие сведения должны предоставляться по запросу к
внешнему источнику (к базе данных или, например, к LDAP), но мы
договорились, что сейчас не обращаем внимания на серверную часть кода.
Поэтому метод getUsers пока будет своеобразной заглушкой, впрочем, не
совсем бестолковой:
private void getUsers(Grid grid) {
int rows;
int newRow;
String rid;
rows=grid.getRowCount();
newRow=grid.insertRow(rows);
grid.setText(newRow, 0, "1");
grid.setText(newRow, 1, "Ivfnov");
grid.setText(newRow,2, "ivanov@gwt.ru");
grid.setText(newRow, 3, "On");
rows=grid.getRowCount();
newRow=grid.insertRow(rows);
grid.setText(newRow, 0, "2");
grid.setText(newRow, 1, "Sidorov");
grid.setText(newRow, 2, "sidorov@gwt.ru");
grid.setText(newRow, 3, "On");
Затем создадим в HTML-шаблоне (MailBox.html) необходимый контейнер:
<div id="mailTable" align="center"></div>
Подключаем нашу таблицу к основной панели приложения:
RootPanel.get("mailTable").add(mailGrid);
После компиляции мы можем видеть ужасно выглядящую и почти бесполезную
таблицу почтовых ящиков (см. рис. 7).
Теперь попробуем привести наше приложение к человеческому виду и
нормальной функциональности. Первая задача большой сложности не
представляет. В GWT по умолчанию для каждого элемента формы есть
соответствующий класс, отображение которого задано в таблице стилей.
Если мы хотим определить стиль самостоятельно, особых проблем не
возникнет. Достаточно воспользоваться соответствующим методом для
установки имени стиля:
mailGrid.setStyleName("emailTables");
И прописать его с таким названием в таблице стилей
(MailBox/war/MailBox.ccs):
emailTables{
font-size: 120%;
line-height: 1em;
background: url(images/hborder.gif) repeat-x;
}
Любуемся результатом (см. рис. 8) и приступаем к созданию функционала.
Сначала обеспечим возможность удаления записи. Создадим виджет кнопки
для удаления как самостоятельный класс, производный от класса Button
(который в свою очередь произведён от Widget):
package com.samag.client;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Grid;
public class delButton extends Button {
public delButton(final Grid grid,final int row) {
super("Delete", new ClickHandler() {
public void onClick(ClickEvent event) {
Window.alert("Удаляем ряд"+row);
grid.removeRow(row);
}
});
}
}
Я думаю, что по аналогии с кнопкой Reset тут всё понятно. Новые только
обращение к объекту Window, аналогу объекта window веб-страницы, и
метод removeRow, который "подсказал" Eclipse. Для того чтобы вставить
эту кнопку в список акаунтов, немного изменяем заполнение таблицы:
newRow=grid.insertRow(rows);
grid.setText(newRow, 0, "1");
grid.setText(newRow, 1, "Иванов");
grid.setText(newRow,2, "ivanov@gwt.ru");
grid.setText(newRow, 3, "On");
grid.setWidget(newRow, 4, new delButton(grid,1));
На следующем этапе сделаем возможным активацию/блокирование акаунтов.
Для этого создадим ещё один виджет - кнопку-переключатель, найдя
предварительно подходящий класс (ToggleButton) в галерее виджетов:
package com.samag.client;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwt.user.client.Window;
public class ActiveButton extends ToggleButton {
public ActiveButton(final Grid grid, int turn) {
super("On", "Off");
if(turn!=1){
this.setDown(true);
}
public void onClick(ClickEvent event) {
Window.alert("Меняем активность");
}
}
Вносим изменение в заполнение таблицы:
newRow=grid.insertRow(rows);
grid.setText(newRow, 0, "1");
grid.setText(newRow, 1, "Ivanov");
grid.setText(newRow,2, "ivanov@gwt.ru");
grid.setWidget(newRow, 3, new ActiveButton(grid,1));
grid.setWidget(newRow, 4, new delButton(grid,1));
Осталось создать возможность заносить в таблицу новые акаунты. Для
этого добавим форму в последнем ряду таблицы. Сначала создадим
необходимые виджеты (которые после компиляции станут полями
html-формы):
final TextBox Name = new TextBox();
final TextBox Email = new TextBox();
final CheckBox Activity = new CheckBox();
Activity.setValue(true);
Создаём новый ряд таблицы и заполняем его виджетами:
int rows=mailGrid.getRowCount();
int newRow=mailGrid.insertRow(rows);
mailGrid.setWidget(newRow, 1, Name);
mailGrid.setWidget(newRow, 2, Email);
mailGrid.setWidget(newRow, 3, Activity);
mailGrid.setWidget(newRow, 4, new Button("Add", new ClickHandler() {
public void onClick(ClickEvent event) {
this.addRow(mailGrid,Name.getValue(),Email.getValue(),Activity.getValue());
}
В последнею ячейку мы помещаем новую кнопку, на этот раз не создавая
нового класса, а просто привязывавя к объекту класса Button новый
хендлер. Метод addRow, конечно, нуждается в реализации:
private void addRow(Grid mailGrid,String uName, String uEmail, Boolean uActivity) {
if(uName.isEmpty() && uEmail.isEmpty()){
Window.alert("Не все параметры заполнены!");
return;
}
int rows=mailGrid.getRowCount();
String rid=String.valueOf(Integer.parseInt(mailGrid.getText(rows-2, 0))+1);
int newRow=mailGrid.insertRow(rows-1);
mailGrid.setText(newRow, 0, rid);
mailGrid.setText(newRow, 1, uName);
mailGrid.setText(newRow, 2, uEmail);
int turn = 0;
if(uActivity.booleanValue()){
turn = 1;
}
ActiveButton uActive = new ActiveButton(mailGrid,turn);
mailGrid.setWidget(newRow, 3, uActive);
mailGrid.setWidget(newRow, 4, new delButton(mailGrid,newRow));
}
Компилируем и проверяем результат (см. рис. 9). Обратите внимание, как
мы совершенно безболезненно перешли с Windows/Chrome на Linux/Firefox.
Для первого знакомства с технологией GWT пока достаточно, но за рамками
осталось, как всегда, самое интересное - взаимодействие с сервером,
передача данных, удалённый вызов процедур, механизм DWR (Direct Web
Remoting) и многое другое. Эти вопросы мы рассмотрим во второй части
статьи.
1. Домашняя страница проекта -
http://code.google.com/intl/ru/webtoolkit
2. Документация по GWT -
http://code.google.com/intl/ru/webtoolkit/doc/1.6/DevGuide.htmlhttp://code.google.com/intl/ru/webtoolkit/doc/latest/DevGuide.html
3. Доклад "Архитектура Google Web Toolkit: полезные советы по
написанию приложений на GWT" на конференции Google Developer Day 2009,
Москва - http://www.youtube.com/watch?v=sjyy9WgaObc&feature=channelа
Статью можно обсудить на Форуме журнала "Системный администратор" http://www.samag.ru/forum/