Ajax для разработчиков Java: Часть 4. Работа с Google Web Toolkit

Разрабатываем Ajax-приложения из базы кода на языке Java

GWT - полный набор API-интерфейсов и средств, которые помогут создать вам динамические Web-приложения на языке Java. Филипп Маккарти возвращается к своей Ajax-серии для Java-разработчиков, чтобы показать, как GWT может помочь вам.

Филипп Маккарти (Philip McCarthy), консультант и разработчик программного обеспечения, независимый специалист

Филипп Маккарти (Philip McCarthy) - консультант и разработчик программного обеспечения, специализирующийся в области языка Java и Web-технологий. В данный момент он работает в проекте Hewlett Packard над Digital Media Platform в HP Labs, Бристол. В течение последних лет Фил разработал несколько толстых Web-клиентов, применяя асинхронную связь между сервером и машиной сценариев DOM. Он рад, что сейчас у нас есть название для этого. Вы можете связаться с Филом по e-mail: philmccarthy@gmail.com.



27.02.2007

У GWT (см. Ресурсы) очень необычный подход к разработке Web-приложений. Кроме того, что GWT использует нормальное разделение кода сервера и кода машины клиента, он также обеспечивает Java-интерфейсами приложений (API), которые помогают вам создавать составные графические интерфейсы (GUI), которые будут отображаться и выполняться на Web-браузере пользователя. Использование GWT значительно ближе к разработке в Swing или SWT чем обычная разработка Web-приложений. Такой подход приводит к попытке абстрагироваться от HTTP-протокола и HTML DOM-модели. В самом деле, обстоятельство того, что приложение прерывается, выполняемое в Web-браузере, является несущественным.

GWT достигает этих целей, генерируя код при помощи компилятора GWT, создающего JavaScript-код из Java-кода на машину пользователя. Он поддерживает данные java.lang и java.util пакетов, вместе с тем понимает API-интерфейсы, которые дает сам GWT. Компилированное GWT-приложение состоит из фрагментов HTML, XML и JavaScript. Однако, их практически невозможно прочитать, поэтому скомпилированное приложение лучше всего рассматривать как "черный ящик" - GWT-эквивалент бинарного представления Java.

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

Начать просто

Листинг 1 показывает код простейшего приложения, которое вы можете написать при помощи GWT:

Листинг 1. Простейший пример GWT
public class Simple implements EntryPoint {

   public void onModuleLoad() {
     final Button button = new Button("Say 'Hello'");

     button.addClickListener(new ClickListener() {
        public void onClick(Widget sender) {
        Window.alert("Hello World!");
        }
     });

     RootPanel.get().add(button);
   }
}

Это выглядит, в целом, как код GUI-интерфейса, который вы, должно быть, уже писали в Swing, AWT или SWT. Как вы уже могли догадаться, листинг 1 создает кнопку, которая выводит на экран сообщение "Hello World!", когда вы на нее нажимаете. Эта кнопка добавлена в RootPanel, GWT оболочку HTML-страницы. Рисунок 1 показывает приложение в действии, выполняемое внутри оболочки GWT Shell . Shell - главная среда отладки, объединенная с простым Web-браузером, который включен в GWT SDK.

Рисунок 1. Простейший GWT-пример в действии
Рисунок 1. Простейший GWT-пример в действии.

Рисунок 1. Простейший GWT-пример в действии

Я собираюсь использовать GWT, чтобы создать простое приложение обозревателя погоды. В графическом интерфейсе пользователю предоставляется форма для ввода ZIP-кода и выбор представления температур по Цельсию или Фаренгейту. Когда пользователь нажимает кнопку "Submit" (подтвердить), приложение использует бесплатный сервер погоды Yahoo!, чтобы представить сообщение в RSS-формате для выбранного региона. Из этого документа извлекается HTML-фрагмент и показывается пользователю.

GWT приложения оформлены как модули (module) и должны подчиняться правилам определенной структуры. Файл конфигурации с именем module-name.gwt.xml определяет класс, который действует как вводная строка ссылок приложения и показывает, не унаследованы ли данные из других GWT-модулей. Вы должны поместить файл конфигурации в структуру пакетов приложения на одном уровне с пакетом с названием client, где расположен весь Java-код с машины клиента, и c открытой директорией, которая содержит web-ресурсы проекта, такие как рисунки, CSS и HTML. Наконец, открытая директория должна включать в себя HTML-файл с меткой meta, содержащей присвоенное имя модуля. GWT JavaScript-библиотека среды исполнения (GWT run-time JavaScript library) использует этот файл, чтобы инициализировать приложение.

applicationCreator в GWT генерирует для вас основную структуру, называя ваш вводный класс. Так называемый
applicationCreator developerworks.gwt.weather.client.Weather генерирует схему проекта, которую я могу использовать в качестве начального каркаса для приложения обозревателя погоды. Источником для приложения служит Ant buildfile, содержащий в себе несколько полезных ссылок для работы с GWT проектами, подчиняющихся этой структуре. (см. Загрузка).

Разработка основного GUI-интерфейса

Прежде всего я разработаю основную схему форм пользовательского интерфейса приложений без добавления их функций. Суперкласс, с которым вам придется постоянно сталкиваться в GWT UI-интерфейсе, - это Widget класс. Классы Widget всегда содержатся в Panel, которые сами являются Widget, и поэтому их тоже можно вставить в интерфейс. Различные типы панелей предлагают различные схемы функции. Таким образом, GWT Panel играет схожую роль с Layout в AWT/Swing или с Box в XUL.

Все формы и панели должны быть в конечном итоге прикреплены к Web-странице, которая содержит их. Как вы видели в листинге 1, вы можете присоединить их прямо к RootPanel. С другой стороны вы можете использовать RootPanel, чтобы получить ссылки на HTML-элементы, определенные по их ID или по имени класса. В нашем примере я буду использовать два отдельных HTML DIV элемента, которые называются input-container и output-container. Первый содержит установки интерфейса (UI) для обозревателя погоды, второй собственно показывает сообщение.

Листинг 2 показывает код, необходимый для формирования основной схемы, его должно быть достаточно. HTML форма - простой контейнер для HTML-фрагмента. Это форма, где выводится на экран ответ от сервиса погоды Yahoo! Весь код находится внутри класса Weather метода onModuleLoad(), предусмотренным интерфейсом EntryPoint. Этот метод запрашивается, когда web-страница со встроенным модулем загружается в web-браузер клиента.

Листинг 2. Схема кода для обозревателя погоды
 public void onModuleLoad() {

   HorizontalPanel inputPanel = new HorizontalPanel();

   // Выравниваем дочерние формы ровно посередине панели
    inputPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);

   Label lbl = new Label("5-digit zipcode: "); inputPanel.add(lbl);

   TextBox txBox = new TextBox(); txBox.setVisibleLength(20);

   inputPanel.add(txBox);

   // Создаем группу кнопок, чтобы выбрать представление погоды -  C или F    
   Panel radioPanel = new VerticalPanel();

   RadioButton ucRadio = new RadioButton("units", "Celsius"); 
   RadioButton ufRadio = new RadioButton("units", "Fahrenheit");

   // По умолчанию Цельсий 
   ucRadio.setChecked(true);

   radioPanel.add(ucRadio); radioPanel.add(ufRadio);

   // Добавляем ввод для панели кнопок
    inputPanel.add(radioPanel);

   // Создаем кнопку "подтвердить" (Sumbit)
    Button btn = new Button("Submit");    

   // Добавим кнопки для ввода информации, выровненные снизу
    inputPanel.add(btn); 
    inputPanel.setCellVerticalAlignment(btn, 
       HasVerticalAlignment.ALIGN_BOTTOM);

   RootPanel.get("input-container").add(inputPanel);

   // Создаем форму для вывода данных в формате HTML 
   HTML weatherHtml = new HTML();

   RootPanel.get("output-container").add(weatherHtml); }

Рисунок 2 показывает схему в GWT-оболочке:

Рисунок 2. Основная GUI-схема
Основная GUIсхема

Добавление CSS-стилей

Наша получившаяся Web-страница выглядит достаточно скучно, поэтому было бы весьма полезно использовать CSS-стили. Вы можете применить несколько методов стилей в GWT-приложении. Во-первых, по умолчанию у каждой формы есть название класса формы project-widget. К примеру, gwt-Button и gwt-RadioButton - два названия классов центральных GWT-форм. Функции панелей в большинстве случаев выполняются в беспорядке и не имеют по умолчанию имен классов.

Подход определенных изначально типов имени классов, соответствующим формам, облегчает стилизование форм в вашем приложении. Конечно, при использовании обычных таблиц стилей (CSS) вы можете применять различные стили для одинаковых типов форм в зависимости от их контекста. Для более гибких условий вы можете переместить или дополнить имена классов форм, принятые по умолчанию, на основе ad-hoc при помощи вызова их методов: setStyleName() и addStyleName().

Листинг 3 применяет сразу оба подхода, чтобы стилизовать панель ввода информации в приложении обозревателя погоды. Имя класса weather-input-panel создается в Weather.java при помощи вызова inputPanel.setStyleName("weather-input-panel");.

Листинг 3. Применение CSS-стилей к панели ввода информации в приложении обозревателя погоды.
/* Стилизуем саму панель ввода информации  */
.weather-input-panel {
   background-color: #AACCFF;
   border: 2px solid #3366CC;
   font-weight: bold;
}

/* Применяем стиль padding (грунтование) для каждого 
элемента панели ввода информации */
 .weather-input-panel * {
     padding: 3px; }

/* Переписываем стиль кнопки, который стоял по умолчанию */ 
.gwt-Button {
   background-color: #3366CC;
   color: white;
   font-weight: bold;
   border: 1px solid #AACCFF;

/* Применяем эффект парение в воздухе для кнопки */ 
.gwt-Button:hover {
   background-color: #FF0084;
}

Рисунок 3 показывает приложение снова, с примененными стилями:

Рисунок 3. Панель ввода информации с примененными стилями
Панель ввода данных с примененными стилями

Добавление функций для выполнения на машине клиента

Теперь, когда основная схема и стиль приложения продуманы, я начну применять функции для выполнения на машине клиента. Вы уже знакомы с флажком состояния, поддерживаемым в GWT, для выполнения действия. GWT обеспечивает интерфейсы Listener для щелчков мыши, нажатия клавиатуры, изменения событий и тому подобного, а также несколько прикладных и полезных классов для более удобного использования.

В большинстве случаев вы добавляете счетчики событий, используя средства внутренних классов (inner-class idiom) без названия, знакомые Swing-программистам. Однако, первый параметр всех GWT-методов Listener - параметр события, в большинстве случаев - это форма, с которой пользователь работает всё время. Это значит, что вы можете прикрепить данные типа Listener к составным формам, где это необходимо, а также использовать параметр события, чтобы определить, какой из параметров вызвал событие.

Листинг 4 показывает выполнение двух счетчиков событий в приложении обозревателя погоды. Обработчик нажатия мыши добавляется к кнопке Submit, а ключ этого действия добавляется в TextBox. Нажатие на кнопку Submit или на клавишу Enter ведет к тому, что из задействованного нажатием TextBox вызывается закрытый (private) метод validateAndSubmit(). В добавление к коду в листинге 4, txBox и ucRadio стали данными класса Weather, поэтому к ним можно иметь доступ из метода проверки на действительность.

Листинг 4. Добавление функций для выполнения на машине клиента
 // Создаем кнопку Submit вместе с прикрепленным к ней 
 // внутренним классом счетчика щелчков 
 Button btn = new Button("Submit", new ClickListener() {

   public void onClick(Widget sender) {
      validateAndSubmit();
   }
});

// Для удобства также подтвердим данные, когда пользователь 
// во включенном окне textbox нажимает на Enter 
txBox.addKeyboardListener(new KeyboardListenerAdapter(){

   public void onKeyPress(Widget sender, char keyCode, int modifiers) {

      // Проверим, что  Enter - ключ 
       if ((keyCode == 13) '' (modifiers == 0)) {
         validateAndSubmit();
      }        
   }      
});

Листинг 5 показывает выполнение метода validateAndSubmit(). Достаточно просто проверить правильность данных при помощи класса ZipCodeValidator. Если пользователь не ввел правильный пятизначный индекс (ZIP-код), то validateAndSubmit() выводит на экран сообщение ошибки в окошке, выраженное в листинге GWT-вызовом Window.alert(). Если ZIP код верен, то код и выбор представления температуры пользователем передаются методу fetchWeatherHtml(), который я напишу немного попозже.

Листинг 5. Логика действий для validateAndSubmit
       private void validateAndSubmit() {

   // Очистим форму ввода данных
    String zip = txBox.getText().trim();

  if (!zipValidator.isValid(zip)) {
     Window.alert("Zip-code must have 5 digits");
     return;

   // Прекратим действие
    TextBox txBox.setEnabled(false);

   // Выберем представление температуры
    boolean celsius = ucRadio.isChecked();
    fetchWeatherHtml(zip, celsius); }

Отладка на машине клиента в оболочке GWT

Я отклонюсь немного в сторону, напоминая, что GWT-оболочка имеет JVM-методы, которые позволят вам отладить код на машине клиента в Java IDE. Вы можете работать с вашим графическим Web-интерфейсом и видеть Java-код, который представляет собой соответствующий клиентскую часть JavaScript-кода. Это важное замечание, так как отладка сгенерированного JavaScript-кода - это практически невыполнимая задача.

Легко конфигурировать Eclipse-задачу отладки кода, чтобы выполнить ее при помощи класса com.google.gwt.dev.GWTShell в оболочке. Рисунок 4 показывает реализацию в Eclipse, остановленную на моменте действия метода validateAndSubmit() следующего за щелчком по кнопке Submit:

Рисунок 4. Отладка GWT-кода на машине клиента в Eclipse
Eclipse, остановленная на действии метода

Взаимодействие с составляющими сервера

Сейчас наш обозреватель погоды может собирать и проверять введенную пользователем информацию. Следующий шаг - взять данные с сервера. В обычной разработке в Ajax это влекло бы за собой целую систему вызова ресурсов сервера прямо из JavaScript-кода и получение данных, обратно закодированных в JSON или XML. GWT, абстрагируясь от такого длительного процесса взаимодействия с сервером, использует свой собственный RPC-механизм вызова удаленных процедур.

В терминологии GWT клиент взаимодействует со службами (services), работающими на Web-сервере. RPC-механизм, использовавший эти методы, сродни подходу Java RMI. Это значит, что вам необходимо написать только код для выполнения на сервере и сделать несколько интерфейсов. Генерация кода и перенос его на машину клиента облегчит задачу создания кода на машине клиента из схемы программы на сервере.

Следовательно, первый шаг - определить интерфейс для службы обозревателя погоды. Этот интерфейс должен продолжать GWT-интерфейс RemoteService и содержать значения методов служб, которые будут выполняться в коде клиента. Так как RPC-вызовы в GWT выполняются между JavaScript-кодом и Java-кодом, GWT содержит в себе механизм сериализации объектов как инструмент, который служит посредником и возвращает значения, несмотря на различие языков (см. Serializable types чтобы видеть то, что используете).

Типы, которые могут быть использованы при сериализации

Краткое изложение типов, которые можно использовать при сериализации в GWT, следующие:

  • Основные классы (такие как int) и основные wrapper-классы (такие как Integer) - сериализуемы.
  • String и Date - сериализуемы.
  • Массивы сериализуемых типов - сами сериализуемы.
  • Классы, определенные пользователем как сериализумые, даже если все его стационарные значения - сериализумы, и они выполняют GWT-интерфейс IsSerializable.
  • Collection классы могут быть использованы в объединении с Javadoc-ссылками, которые утверждают сериализуемые типы, содержащиеся в них.

Так как код клиента ограничен малым набором подмножеств Java-класса, выполняемый в любом случае GWT, эти сериализуемые типы дают достаточно полный обзор.

После определения интерфейса службы следующим шагом будет выполнение его в классе, который является продолжением GWT-класса RemoteServiceServlet. Предопределение имени является особенностью написанного на языке Java HttpServlet, поскольку он может быть привязан к любому конструктору сервлета.

Специфика GWT стоит того, чтобы упомянуть о ней здесь. Она состоит в том, что удаленный интерфейс службы должен быть записан в вашем пакете приложения client, так как необходимо, чтобы он стал частью JavaScript-кода в ходе процесса генерации кода. Однако, так как выполнение класса на сервере ссылается на удаленный интерфейс, время компилирования будет зависеть от процесса прохождения между кодом сервера и кодом клиента. Мое решение этого вопроса - поместить удаленный интерфейс в common подпакет пакета client. Затем я включу common в Java-версию, но остаток пакета я удалю. Это избавит файлы класса от обратной генерации из кода клиента, ибо нам требуется только конвертирование в JavaScript-код. Более изящное решение могло бы состоять в том, чтобы расщепить структуру пакетов на сервере на две директории для кода клиента и кода сервера и дублировать обычные классы в обеих директориях.

Листинг 6 показывает интерфейс удаленной службы, используемый в приложении обозревателя погоды WeatherService. Он понимает ZIP-код и флаг Цельсия\Фаренгейта как данные ввода и возвращает содержимое типа String с описанием погоды в формате HTML. Листинг 6 также показывает основные принципы класса YahooWeatherServiceImpl, который использует API-интерфейс Yahoo!, чтобы исходя из заданного ZIP-кода получить сообщение о погоде в RSS-формате и взять из него HTML-описание.

Листинг 6. Удаленный интерфейс WeatherService и частичное выполнение
 public interface WeatherService extends RemoteService {

   /** 
   * Возвращает описание погоды в формате HTML 
   * @param параметр Zip код для получения погоды 
   * @param параметр Цельсий стоит по умолчанию 
   * возвращает описание погоды в формате HTML, учитывая район ZIP-кода */ 
   public String getWeatherHtml(String zip, boolean isCelsius)
   	 throws WeatherException; 
   } 

public class YahooWeatherServiceImpl extends RemoteServiceServlet
      implements WeatherService {

   /**
    * Возвращает описание погоды в формате HTML 
    * @param параметр Zip код для получения погоды 
    * @param параметр Цельсий стоит по умолчанию 
    * возвращает описание погоды в формате HTML, учитывая район ZIP-кода */
    public String getWeatherHtml(String zip, boolean isCelsius) 
    	throws WeatherException {

     // Здесь идет код } }

Мы несколько отклонимся от стандартного RMI-подхода к этому вопросу. Так как Ajax-вызовы из JavaScript-кода асинхронны, то потребуется некоторая дополнительная работа, чтобы определить асинхронный интерфейс, который код клиента использует для запроса службы. Значение асинхронного метода интерфейса отличается от методов удаленного интерфейса, поэтому GWT полагается на совпадающие наименования (Magical Coincidental Naming). Другими словами нет синхронизации среды компиляции между асинхронным интерфейсом и удаленным интерфейсом, но GWT вычисляет его с помощью соглашения об именах. Листинг 7 показывает асинхронный интерфейс для WeatherService:

Листинг 7. Асинхронизированный интерфейс для WeatherService
 public interface WeatherServiceAsync {
 
   /** * Получим описание погоды в формате HTML, внесем значение
    * @param параметр Zip код для получения погоды 
    * @param параметр Цельсий стоит по умолчанию 
    * "flase" для значения Фаренгейт 
    * @param данные о погоде будут переданы этому обработчику обратной связи */ 
    public void getWeatherHtml(String zip, boolean isCelsius, 
      AsyncCallback callback);
}

Как вы видите, основная идея - это создать интерфейс, называемый MyServiceAsync и дополнить его для каждого значения метода, убрать неиспользуемые типы, выполняющие обратный переход в Java-код с JavaScript-кода и добавить дополнительный параметр типа AsyncCallback. Асинхронный интерфейс должен также находиться в пакете, как и удаленный интерфейс. В классе AsyncCallback есть два метода: onSuccess() и onFailure(). Если вызов службы успешно выполнен, то запрашивается метод onSuccess() с возвращением значения вызова службы. Если удаленный вызов провалился, то запрашивается метод onFailure() и вызывается Throwable, сгенерированный службой для предоставления причины ошибки.


Запрос службы с машины клиента

С WeatherService и его асинхронным интерфейсом я могу модифицировать клиента обозревателя погоды, чтобы запросить службу и проследить ее выполнение. Первый шаг - это всего лишь составление формы для кода установки: она создает данные WeatherServiceAsync для клиента погоды, которые будут использоваться при вызове GWT.create(WeatherService.class) и соответствующего ему объекта, возвращающего его значение. Следующим шагом WeatherServiceAsync нужно поместить в ServiceDefTarget так, чтобы можно было вызывать в нем setServiceEntryPoint(). setServiceEntryPoint() указывает части WeatherServiceAsync на адрес, где выполняется ему соответствующая удаленная служба. Запомните, что этот процесс чрезвычайно жестко запрограммирован в процессе компиляции. Так как этот код преобразуется в JavaScript, выполняемый в Web-браузере, нет возможности посетить эту ссылку в свойствах файла во время выполнения. Понятно, что это увеличивает громоздкость скомпилированного GWT Web-приложения.

Листинг 8 показывает формирование объекта WeatherServiceAsync и затем задает выполнение метода fetchWeatherHtm(), про который я упомянул выше (см. Добавление функций для выполнения на машине клиента):

Листинг 8. Использование RPC-службы для запроса удаленной службы
 // Статичная конфигурация RPC-службы 
 private static WeatherServiceAsync ws = 
   (WeatherServiceAsync) GWT.create(WeatherService.class);
static {
   ((ServiceDefTarget) ws).setServiceEntryPoint("ws");
}
/** 
* Асинхронный вызов службы и вывод на экран результатов */ 
private void fetchWeatherHtml(String zip, boolean isCelsius) {

   // Спрятать существующее сообщение о погоде 
   hideHtml();

   // Вызов удаленного сервиса и определить
 выполнение обратного вызова 
   ws.getWeatherHtml(zip, isCelsius, new AsyncCallback() {
      public void onSuccess(Object result) {

         String html = (String) result;
         // Показать новое сообщение о погоде 
         displayHtml(html);
      }

      public void onFailure(Throwable caught) {
         Window.alert("Error: " + caught.getMessage());
         txBox.setEnabled(true);
       }
   });
}

Когда выполняется вызов метода getWeatherHtml() службы, создается соответствующий ему класс обратного вызова без имени, передающий запрос сервера методу, который выводит его на экран.

Рисунок 5 показывает приложение в действии, выводя на экран сообщение о погоде, которое было взято с API-интерфейса погоды от Yahoo!:

Рисунок 5. Приложение обозревателя погоды (Weather Reporter), выводящее на экран сообщение погоды, взятое с Yahoo!
Приложение в действии с выводом на экран сообщения о погоде

Необходимые действия для подтверждения на сервере

Объединение клиентской части GWT-кода и кода на сервере по сути опасно. Так как вы программируете все на языке Java в абстракции GWT, скрывающей такое соединение клиент-сервер, очень легко сбиться с толку, думая, что ваш клиентский код не даст сбоя во время выполнения приложения. Это ошибка. В любой код, который выполняется в Web-браузере, может умышленно вмешаться пользователь или его можно потерять в процессе передачи. GWT обеспечивает высокий уровень шифрования, которое в разы облегчает проблему, но другой аспект проблемы остается: HTTP-трафик, который идет между GWT-клиентом и его службами и обратно.

Предположим, я атакую приложение обозревателя погоды, желая выявить его слабые места и недостатки. Рисунок 6 показывает средство Fiddler от корпорации Microsoft, перехватывающее запрос от клиента обозревателя погоды к WeatherService, запущенному на сервере. Уловив запрос, Fiddler поможет вам изменить любую часть запроса. Текст, выделенный синим цветом, показывает, что я обнаружил, где зашифрован ZIP-код. Теперь я могу изменить его на что угодно, хоть "10001" на "XXXXX."

Рисунок 6. Использование Fiddler для изменения данных клиента, приводящего к ошибкам
Работа с Fiddler при запросе от клиента

Сейчас предположим, что некоторый простой код на сервере в YahooWeatherServiceImpl вызывает по ZIP-коду метод Integer.parseInt(). После этого ZIP-код должен пройти проверку на действительность, которая проводится с помощью метода validateAndSubmit() класса Weather, правильно? Как вы увидели, эта проверка была нарушена и теперь NumberFormatException не работает.

В этом случае ничего страшного не произошло, и производящий атаку просто видит сообщение об ошибке в клиенте. Однако, для целого класса атак существует возможность навредить более важным данным в GWT-приложениях. Представьте, что ZIP-код стал, к примеру, ID-номером покупателя в приложении обрабатывания заказа в порядке очереди. Где бы ни было использовано значение в запросе базы данных, такой подход допускает возможность SQL-атак.

Ничего из сказанного выше не является чем-то новеньким для того, кто прежде уже работал с Ajax-приложениями. Вам просто необходимо несколько раз проверить все значения ввода данных, перепроверяя их на сервере. Главное - это помнить, что некоторые части Java-кода, которые вы пишете в вашем GWT-приложении, ненадежны во время выполнения. Однако, к этому приложению комар носа не подточит, так как в нем я уже написал ZipCodeValidator для использования на машине клиента, поэтому я могу просто добавить его в мой пакет client.common и еще раз использовать аналогичный прием проверки на сервере. Листинг 9 показывает эту проверку, код которой добавлен в YahooWeatherServiceImpl:

Листинг 9. Добавление ZipCodeValidator в YahooWeatherServiceImpl
 public String getWeatherHtml(String zip, boolean isCelsius) 
       throws WeatherException {

   if (!new ZipCodeValidator().isValid(zip)) {
      log.warn("Invalid zipcode: "+zip);
      throw new WeatherException("Zip-code must have 5 digits");
   }

Вызов изначального JavaScript-кода при помощи JSNI

Библиотеки визуальных эффектов становятся очень популярны в разработке Web-приложений несмотря на то, используются ли эти эффекты, чтобы обеспечить точное взаимодействие с пользователем или просто добавить глянца. Мне бы хотелось добавить несколько приглядевшихся эффектов в приложение обозревателя погоды. И, хотя GWT не обеспечивает такой тип функциональности, JavaScript-интерфейс (JSNI) дает решение этого вопроса. Он позволяет вам вызвать JavaScript-код прямо из Java-кода в GWT-клиенте. Это обозначает, что я могу использовать эффекты из Scriptaculous-библиотеки (см. Ресурсы) или, к примеру, из библиотеки интерфейса пользователя от Yahoo!.

JSNI использует в работе грамотную комбинацию native ключа в языке Java и JavaScript, вставленном в специальном блоке комментария. Возможно, лучше всего это объяснить на примере, поэтому листинг 10 показывает метод, который выполняет заданный Scriptaculous-эффект в Element:

Листинг 10. Запрос Scriptaculous-эффектов с помощью JSNI
/** 
* В окне сообщения о погоде выводим данные в формате HTML
*/ 
private void displayHtml(String html) {
   weatherHtml.setHTML(html);
   applyEffect(weatherHtml.getElement(), "Appear");
}

/** 
* Применяем Scriptaculous-эффект  к элементу 
* @param element The element to reveal 
*/ 
private native void applyEffect(Element element, String effectName) /*-{

   // Инициируем выбранный Scriptaculous-эффект  
   $wnd.Effect[effectName](element); 
 }-*/;

Это точно действующий Java-код, так как компилятор видит только private native void applyEffect(Element element, String effectName);. GWT тщательно анализирует содержимое блока комментария и выводит точный JavaScript-код. GWT обеспечивает процесс, в котором $wnd и $doc переменные ссылаются на объекты окна и документа. В этом случае я просто получаю доступ к объекту верхнего Scriptaculous-уровня Effect и использую синтаксис квадратных скобок в объекте доступа для запроса соответствующей функции, предопределенную вызовом. Тип Element "волшебный" тип, предусмотренный GWT, который представляет лежащий в основе WidgetHTML DOM-элемент как в Java, так и в JavaScript-коде. String - один из несколько других типов, которые могут точно переводиться при помощи JSNI из Java в JavaScript-код, и обратно.

Теперь у меня есть усовершенствованное сообщение о погоде, когда данные возвращаются с сервера. Последний штрих - отменить запрет TextBox ZIP-кода, когда действие закончилось. Scriptaculous использует асинхронный механизм обратного вызова, чтобы уведомить счетчики о закончившемся цикле. Здесь всё несколько сложнее, потому что мне нужно возвратить обратно вызов JavaScript в мой Java-код в GWT-клиенте. В Javascript вы можете запросить любую функцию с произвольным количеством аргументов, поэтому нет перегрузки методов Java. Это обозначает, что JSNI необходимо использовать объемный синтаксис для ссылок на Java-методы, чтобы разргрузить приложение от возможных перегрузок. FWT-документация утверждает следующий синтаксис:

[instance-expr.]@class-name::method-name(param-signature)(arguments)

instance-expr. часть здесь более всего уместна, так как статичные методы вызываются без необходимости ссылки на объект. Проще всего опять увидеть это, проиллюстрировав примером, в листинге 11:

Листинг 11. Обратный вызов в Java код с помощью JSNI
/** 
* Применяем эффект Scriptaculous к элементу 
* @param элемент - элемент для обнаружения 
*/ 
private native void applyEffect(Element element, 
String effectName) /*-{

  // Сохраняем ссылку для себя в случае
 прерывания для пользования локально 
  var weather = this;

  // Инициируем выбранный Scriptaculous-эффект  
  $wnd.Effect[effectName](element, { afterFinish : function () {

     // Делаем обратный вызов объекта погоды 
     weather.@developerworks.gwt.weather.client.Weather::effectFinished()();
     } 
  });
}-*/;

/** 
* Обратный вызов, инициированный,
 когда заканчивается Scriptaculous-эффект  
* Восстанавливаем окошко ввода информации. 
*/ private void effectFinished() {
  this.txBox.setEnabled(true);
  this.txBox.setFocus(true);
}

applyEffect() метод был изменен, чтобы добавить дополнительный afterFinish аргумент в Scriptaculous. Значение afterFinish - асинхронная функция, которая вызывается, когда эффект применен. Это очень похоже на стиль внутреннего класса без имени, используемого GWT-счетчиками событий. В процессе выполняется применяется обратный вызов, переводящий в Java-код, и определяются данные объекта Weather, запрашивающего вызов, затем полностью соответствующее имя класса Weather, затем имя вызываемой функции. Первая пустая пара скобок обозначает, что я хочу вызывать метод, называющийся effectFinished(), который не имеет аргументов. Второй набор скобок вызывает функцию.

Некоторая игра слов здесь состоит в том, что локальная переменная weather хранит копию this ссылки. По причине того, что действует семантика JavaScript-вызова, переменная this внутри afterFinish функции - действительно Scriptaculous-объект, так как Scriptaculous вызывает функцию. Копирование this за пределами действия - просто искусственный прием.

Учитывая то, что я продемонстрировал некоторые возможности JSNI, мне следует показать более хороший способ интегрирования Scriptaculous в GWT, задействуя функциональность Scriptaculous-эффектов как обычную GWT-форму. Это сродни с тем, что Алексей Соколов сделал в GWT-библиотеке компонентов (GWT Component Library) - см. Ресурсы).

Теперь, когда я все сделал для приложения обозревателя погоды, я рассмотрю некоторые преимущества и недостатки Web-разработки при помощи GWT.


Для чего использовать GWT?

Вопреки тому, что вы могли ожидать, GWT-приложения пригодны совершенно не для Web. GWT действительно по существу использует браузер как среду выполнения для легковесных GUI-приложений, и результат в большей степени схож с тем, если бы вы разрабатывали при помощи Morfik, OpenLaszlo, или даже Flash, чем в случае с обычными Web-приложениями. Следовательно, GWT больше всего подходит для Web-приложений, которые могут существовать в богатом Ajax GUI-интерфейса на одной странице. Вероятно, неслучайно, что это характеризует некоторые последние Google бета-версии, такие как приложения календаря (Calendar) и программы табличных вычислений (Spreadsheets). Это достаточно хорошие приложения, но вы не можете решить все бизнес-сценарии, используя этот подход. Хотя возможно комбинировать GWT-формы с обычными HTML-формами для ввода данных, состояние GWT-формы отражается в конце страницы. К примеру, не существует прямого способа подтвердить выбранное значение из GWT формы Tree как часть стандартной формы.

Лицензия

GWT-библиотеки выполнения лицензированы Apache License 2.0, и вы можете использовать GWT свободно, чтобы создавать коммерческие приложения. Однако, GWT-панель инструментов представлена только в бинарной форме, и модификации ее не разрешены. Это верно и для компилятора Java-кода в JavaScript, и обратно, что обозначает, что любые ошибки в вашем сгенерированном JavaScript-коде неконтролируемы. Особая проблема взаимодействия пользователя и GWT-разработчиков: каждая версия нового браузера требует обновления GWT-средств, чтобы обеспечить поддержку.

Если вы все-таки решили использовать GWT в J2EE-среде приложений, следует сделать объединение GWT-разработок четко и ясно. В этом сценарии GWT-службы должны быть продуманы как похожие на Action в Strut - тонкий промежуточный слой, который просто преобразует Web-запросы, в конце концов, в вызовы управления и анализа. Поскольку GWT-службы - всего лишь HTTP сервлеты, они могут быть запросто объединены в Struts или SpringMVC, к примеру, и помещены после фильтров аутентификации.

У GWT действительно есть пара значительных недостатков. Первый из них - недостаточность обеспечения для удачного разделения. Лучшей практикой в разработке современных Web-приложений считается создание страниц, которые работают без JavaScript-кода, и затем используют его там, где необходимо украсить и добавить дополнительные функции. В GWT, если не использовать JavaScript, вы не можете получить UI-интерфейса вообще. Для некоторых классов Web-приложений это прямой удар. Интернационализация - также большая проблема для GWT. Поскольку Java-классы GWT-клиента запускаются в браузере, они могут не иметь доступа к возможностям или к узлам источников, чтобы взять находящиеся там строки кода во время выполнения. Конечно, можно применить сложный искусственный прием, который потребует субкласса для создания каждого класса клиента в каждом месте действия (см. Ресурсы), но GWT-инженеры работают над более эффективным решением этой проблемы.

В случае генерации кода

Возможно, самый спорный аспект GWT-архитектуры - преобразование языка Java в код клиента. Некоторые GWT-сторонники говорят, что написание кода клиента на языке Java по сути предпочтительнее, чем написание кода на JavaScript. Это вовсе не всесторонний взгляд на проблему, и несколько JavaScript-разработчиков могли бы с большой неохотой пожертвовать гибкостью и выразительностью их языка ради временами обременительных заданий по разработке на языке Java. Единственная ситуация, в которой замещение JavaScript на Java-код было бы привлекательным, - в команде, в которой не хватает опытных Web-разработчиков. Однако, если эта команда будет двигаться в сторону Ajax-разработок, для нее будет лучше, если нанимать опытных JavaScript-программистов, а не полагаться на Java-программистов для производства JavaScript. Ошибки, вызываемые недостатками знания более высокого уровня абстракции GWT, нежели чем JavaScript, HTTP и HTML, неизбежны, и неопытные Web-программисты потратят много усилий и времени, искореняя их. Как разработчик и блоггер Дмитрий Глазков замечает в этом случае: "Если вы не можете работать с JavaScript, вам не следует писать код для Web-приложений. HTML, CSS и JavaScript - три необходимых условия для этого." (см. Ресурсы).

Некоторые люди считают, что написание кода на языке Java заведомо меньше подвержено ошибкам, нежели программирование в JavaScript благодаря статичной печати и проверкам во время компиляции. Это довольно обманчивый аргумент. Можно написать плохой код на любом языке, и множество Java-приложений с ошибками доказывает это. Вы также зависите от GWT-генерации кода, которая может выдавать ошибки. Однако, независимая проверка синтаксиса кода клиента может быть определенно полезной. Это выполняется для JavaScript в форме JSLint Дугласа Крокфорда (см. Ресурсы). GWT стоит на шаг впереди по тестированию, несмотря на Junit-интеграцию кода клиента. Поддержка тестирования - область, где JavaScript сильно проигрывает.

В процессе разработки приложения обозревателя погоды самым ярким событием в Java-коде клиента оказалось открытие данных класса проверки на правильность между обоими звенами. Это определенно спасает разработчиков. То же самое происходит и с любыми классами, передающимися через RPC, вам только нужно однажды их написать и вы можете использовать их и в клиенте, и на сервере. К сожалению, иногда абстракции недостаточно: в моей проверке действительности ZIP-кода, к примеру, мне захотелось использовать стандартные выражения, чтобы выполнить проверку. Однако, GWT не может выполнить метод String.match(). Даже если бы он мог, стандартные выражения в GWT имеют свои синтаксические отличия применения кода на сервере или кода клиента. Всё это происходит потому, что GWT при работе полагается на лежащий в основе regexp-механизм среды выполнения, и это пример проблемы, которая делает несовершенными ваши абстракции.

Большой плюс, который есть у GWT, - это его RPC-механизм и встроенная сериализация данных для Java-кода и JavaScript. Это освобождает вас от большого объема, который вы наблюдаете в среднем Ajax-приложении. Не без прецедентов, конечно. Если вы хотите видеть эту функциональность без остатка GWT, то очень желательно изучить удаленные web-методы DWR (Direct Web Remoting), который предлагает следование объектов, с помощью RPC определенное, из Java-кода в JavaScript, и обратно (см. Ресурсы).

GWT также выполняет хорошую работу, абстрагируясь от некоторых низкоуровневых аспектов разработки Ajax-приложений, таких как несовместимости браузеров, DOM-модель событий и вызов Ajax-функций. С другой стороны современные средства JavaScript, такие как Yahoo! UI Library, Dojo и MochiKit, обеспечивают подобный уровень абстракции без необходимости прибегать к генерации кода. Более того, все эти средства - открытый источник, поэтому вы можете переделать их, подгоняя под свои нужды или исправляя ошибки, которые возникают. Это невозможно с таким "черным ящиком", как GWT. (см. секцию Лицензия).


Заключение

GWT - полная структура, которая обеспечивает большой набор полезных функций. Однако, GWT - очень похож на подход "все или ничего", нацеленный на относительно узкую нишу на рынке разработок Web-приложений. Я надеюсь, что этот краткий обзор дал вам понимание GWT возможностей и их пределов. Хотя он определенно не подходит для всех нужд, GWT является крупным инженерским достижением и достоин серьезного рассмотрения, когда вы будете разрабатывать следующее Ajax-приложение . GWT, конечно, гораздо шире, чем я постарался показать его здесь, поэтому действительно почитайте Google документацию для большего кругозора и присоединитесь к обсуждению на форуме разработчиков GWT. (см. Ресурсы).


Загрузка

ОписаниеИмяРазмер
GWT приложение обозревателя погодыj-ajax4-gwt-weather.zip2.1KB

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML, Open source, Технология Java
ArticleID=198239
ArticleTitle=Ajax для разработчиков Java: Часть 4. Работа с Google Web Toolkit
publish-date=02272007