вторник, 27 апреля 2010 г.

Google Web Toolkit — современное средство создания Rich Internet Application


Любое Интернет-приложение эпохи web 2.0  — это, прежде всего «богатый» и функциональный пользовательский интерфейс, выполненный с использованием технологии 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.
Рис. 1
GWT работает!
Командой ant hosted мы запускаем приложение в так называемом «размещённом» (hosted) режиме. В нём оно выполняется  на виртуальной машине Java (JVM). Этот режим предназначен для отладки и разработки.
Мы видим два окна встроенного браузера, в первом из них запущено само приложение, во втором отображается отладочная информация, в том числе данные HTTP запросов.
Теперь откомпилируем наше приложение в HTML/JavaScript. Для этого вызовем команду.

ant build                                                                                                  

запускающую  GWT-компилятор. Результат его работы можно увидеть в папке samples/Hello/war, раскрыв в браузере файл MailBoxes.html. Как видим (рис 2) всё прошло успешно. Правда не впечатляет.


 Рис. 2
Скомпилированное GWT приложение
Чтобы увидеть настоящее GWT приложение, заглянем в папку  samples/Mail, где хранятся демонстрационные примеры приложений, проведем аналогичные действия. В результате получим уже нечто вполне приемлемое (рис 3).
 Рис. 3
Почтовый клиент на JavaSxript
Первое приложение
 
Теперь попробуем создать собственное, простенькое GWT приложение. Для этого воспользуемся утилитой webAppCreator, входящей в комплект GWT:

 webAppCreator  -out MailBoxes com.samag.MailBoxes

MailBoxes  -  это название нашего приложения (да, да, интерфейс к почтовому серверу). После выполнения команды будет создано несколько папок и файлов в папке MailBoxes/, которые составят скелет приложения и обеспечат базовый, «Hello GWT» функционал:


C:\www\gwt>webAppCreator  -out MailBoxes com.samag.MailBoxes
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). Как видите, оно состоит из поля ввода, куда следует поместить имя, и кнопки «Отправить», после нажатия на которую появляется ответ сервера, с приветствием, учитывающим ваши данные.
 Рис. 4
Наше первое GWT приложение
Давайте теперь посмотрим, что у него внутри.
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 Bdutton sendButton = new Button("Send");  
//создаёние кнопки 
sendButton.addStyleName("sendButton");
// привязка её к таблице стилей 
RootPanel.get("sendButtonContainer").add(sendButton);
// добавление в панель приложения


Я не собираюсь пересказывать документацию, ссылка на которую дана в конце статьи, но базовые понятия изложить пройдется.
Основной класс приложения (в нашем случае  MailBoxes), реализует интерфейс  EntryPoint. Его метод onModuleLoad(), вызывается в тот момент, когда веб-страница, с встроенным GWT модулем, отображается в браузере клиента.
Все элементы визуального интерфейса (кнопки, поля ввода, чекбоксы и т д.), унаследованы от суперкласса – Widget. Все эти виджеты компонуются в рамках объекта класса Panel, который, разумеется, сам является виджетом (его аналог в .SwingLayout).
Компоновка в пространстве веб-страницы происходит в элементы контейнеры HTML (в данном случае "sendButtonContainer"- это id HTML элемента “”), в который будет помещена кнопка.
Далее добавляется обработчик:


   
        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>

Теперь в  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
Сначала скачаем (http://code.google.com/intl/ru/eclipse/) и установим плагин для IDE 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 и принять лицензионное соглашение.

 Рис 5
Устанавливаем Google Plugin
Теперь импортируем наше приложение в IDE. Для этого нажимаем File> Import, в появившемся окне мастера выбираем  General-> Existsing Project into Workspace. В следующем окне, в поле Select root directory указываем путь, до корня нашего приложения и жмём Finish. Приложение должно появиться в левом окне Eclipse (рис 6).
 Рис. 6
GWT приложение в IDE Eclipse
Для включения поддержки 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) необходимый контейнер:
<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 >

И подключаем нашу таблицу к основной панели приложения:


 RootPanel.get("mailTable").add(mailGrid);


После компиляции, мы можем видеть ужасно выглядящею и, в общем, то почти бесполезную таблицу почтовых ящиков. (Рис 7)

Рис. 7
Выводим список пользователей
Теперь попробуем привести наше приложение к человеческому виду и нормальной функциональности. Первая задача большой сложности не представляет. В GWT, по умолчанию, для каждого элемента формы есть соответствующий класс, отображение которого задано в таблице стилей. Если мы хотим определить стиль самостоятельно, особых проблем не возникнет. Достаточно воспользоваться соответствующим методом, для установки имени стиля:


           mailGrid.setStyleName("emailTables");


И прописать стиль с таким названием в таблице стилей (MailBox/war/MailBox.ccs):


        .emailTables{ 
                    font-size: 120%; 
                    line-height: 1em; 
                    background: url(images/hborder.gif) repeat-x; 
                    }


Любуемся результатом (рис 8) и  приступаем к созданию функционала.

Рис. 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).

Рис. 9
Приложение готово!
На этом пока всё. За пару десятков минут мы создали вполне функциональный веб-интерфейс, написали методы, в которых можно реализовать взаимодействие с сервером.
Для первого знакомства с технологией GWT пока достаточно, но за рамками осталось, как всегда самое интересное – взаимодействие с сервером, передача данных, удалённый вызов процедур, механизм DWR (Direct Web Remoting) и многое другое. Эти вопросы мы рассмотрим во второй части статьи.

Ссылки к статье:
Домашняя страница проекта:

Документация  по GWT:
Доклад «Архитектура Google Web Toolkit: полезные советы по написанию приложений на GWT
», на конференции Google Developer Day 2009, Москва.

Комментариев нет:

Отправить комментарий