Создание Ajax-проекта с использованием инструментария Google Web Toolkit, Apache Derby и Eclipse : Часть 3. Налаживание связи

Удаленные вызовы процедур (RPC) и Google Web Toolkit

В двух предыдущих статьях данной серии мы рассматривали вопросы создания несложного Web-приложения с помощью Google Web Toolkit (GWT) и реляционной базы данных Apache Derby. В первой части, мы сконструировали клиентскую часть для системы заказа пиццы (Slickr). Во второй части, мы попробовали создать реляционную базу данных с помощью Derby, а также основу механизма для преобразования данных в объекты Java™. В этой статье мы попробуем наладить связь между клиентской и серверной частью. Мы будем использовать механизм удаленного вызова процедур с помощью GWT, что позволит нам получать данные с сервера почти так же просто, как вызвать обычный метод в Java.

Ноэл Рэппин, старший инженер-программист, Motorola, Inc.

Ноэл Рэппин (Noel Rappin) имеет степень доктор философии по графике, визуализации и используемости от Georgia Institute of Technology. Он работает старшим инженером-программистом в Motorola, Inc. Является соавтором книг "wxPython в действии" (Manning Publications, март 2006) и "Основные элементы Jython" (O'Reilly, март 2002).



30.01.2008

О главном в Ajax

Ключевая разница между обычным Web-приложением и его аналогом, созданном с помощью технологии Asynchronous JavaScript + XML (Ajax), заключена в слове асинхронность. Ajax-приложение позволяет браузеру обновлять определенную часть страницы, не перегружая ее полностью. Этот простой подход позволяет значительно увеличить интерактивность приложения, делая поведение простых Web-страниц похожим на поведение полноценных приложений.

С точки зрения разработчика, механизм асинхронности имеет два ключевых компонента:

  • Объект XMLHttpRequest является определяемым браузером объектом JavaScript, позволяющим Web-странице посылать HTTP-запросы и получать ответы с помощью фонового потока. В отличие от типичного запроса, данный механизм не прерывает работу пользователя, и браузер не делает паузу во время ожидания ответа.
  • Определенный тип обратного вызова, который выполняется после получения ответа. Этот обратный вызов обычно использует модель объектов документа JavaScript (JavaScript Document Object Model, DOM) для манипуляции элементами на странице, основанной на новых данных. При этом на экране происходят нужные пользователю изменения.

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

Метод XMLHttpRequest/DOM связан с определенными трудностями. С одной стороны, каждый браузер по-разному обрабатывает соответствующие объекты JavaScript. С другой, отправка данных на сервер может быть затруднена, как и преобразование ответа в полезные данные. В результате, каждая серьезная среда разработки с поддержкой Ajax содержит инструменты, упрощающие работу с созданием, выполнением и управлением удаленными вызовами процедур (RPC). GWT не является исключением.

GWT позволяет управлять RPC с помощью многоинтерфейсной инфраструктуры, чем-то напоминающей технологию Enterprise JavaBeans (EJB), но - к счастью - намного проще. Ее суть в определении списка удаленных вызовов, осуществляемых вашей системой. GWT осуществляет фоновую подготовку данных к отправке на сервер, осуществление вызова, и преобразование полученных данных в понятную клиентской части информацию. Затем мы определяем последовательность действий, выполняемую после завершения удаленного вызова. Это не так тривиально, как простой вызов метода Java, но и совсем не сложно.

Изменения, которые мы проделаем в приложении Slicr, будут заключаться в получении начального списка начинок из базы данных сервера, вместо их определения в клиентском коде. Этот простейший пример содержит в себе все шаги, необходимые для осуществления вызова RPC. Для упрощения запуска данного примера, запускайте его в режиме хоста. В режиме хоста GWT автоматически симулирует ваши удаленные вызовы. Вам будет немного проще работать с данным режимом, если вы используете интегрированную среду разработки (IDE) типа Eclipse или IntelliJ IDEA.

Обратите внимание: В последней статье данной серии, я покажу вам, как запустить серверную часть в качестве стандартного сервлета.

Создание сервисов

Основная масса работы при работе с RPC в GWT заключается в определении двух классов. На стороне сервера, мы создаем подкласс RemoteServiceServlet. С его помощью мы манипулируем данными сервера и возвращаем информацию клиенту (или создаем исключения, но давайте пока будем оптимистично смотреть на вещи). На стороне клиента, мы определяем класс, наследующий интерфейс AsyncCallback. В данном классе мы определяем действия клиентской страницы после получения ответа данных (или исключения) от сервера. Вдобавок к этим двум классам, мы должны реализовать связующий код, который позволит осуществлять взаимодействие классов на стороне клиента и сервера. Этот код состоит из двух различных интерфейсов, а так же связующего кода на стороне клиента и нескольких настроек. Однако не стоит беспокоиться: большинство кода является шаблонным. Рассмотрим этот процесс по шагам.

Начнем со стороны сервера. Наша цель - всего лишь создать список начинок, помещенных в базу данных в конце предыдущей статьи. Сервер использует ObjectFactory из той же статьи (см. листинг 1).

Листинг 1. Реализация сервиса для работы со списком начинок
public class ToppingServiceImpl extends RemoteServiceServlet 
		implements ToppingService {

	public static final String DRIVER = "org.apache.derby.jdbc.EmbeddedDriver";

	public static final String PROTOCOL = "jdbc:derby:slicr;";

	public List getAllToppings() {
		try {
			Class.forName(DRIVER).newInstance();
			Connection con = DriverManager.getConnection(PROTOCOL);
			Statement s = con.createStatement();
			ResultSet rs = s.executeQuery(
			        "SELECT * FROM toppings order by name");
			return ObjectFactory.convertToObjects(rs, Topping.class);
		} catch (Exception e) {
			e.printStackTrace();
			return new ArrayList();
		} finally {
			try {
				DriverManager.getConnection("jdbc:derby:;shutdown=true");
			} catch (SQLException ignore) {}
		}
	}

}

Данный код не представляет собой ничего сложного. Требования для удаленного сервиса GWT просты: он должен расширять RemoteServiceServlet и реализовывать интерфейс, который мы создадим двумя параграфами ниже.

Обратите внимание: В большинстве документации по GWT интерфейс создается в первую очередь. На самом деле, порядок не имеет особого значения. Я считаю, что привести конкретный код в данной ситуации будет более разумно для лучшего понимания.

В то же время, данный код имеет почти ту же функциональность, что и пример ToppingTestr из предыдущей статьи, адаптированный для использования с GWT. Мы обращаемся к базе данных Derby, используем генератор объектов для создания объектов, имитирующих начинки для пиццы, и возвращаем эти объекты.

Связующий код

Для того чтобы наш новый сервис был доступен клиентскому приложению, нам необходимо определить два родственных интерфейса. Первый из них - это ToppingService, использовавшийся, но не описанный, в листинге 1. Он очень прост и показан в листинге 2.

Листинг 2. Определение первого связанного интерфейса, ToppingService
public interface ToppingService extends RemoteService {
	public List getAllToppings();
}

Все, что нам здесь необходимо сделать - это использовать ту же сигнатуру, что и для метода в конкретном классе. Этот интерфейс должен расширять com.google.gwt.user.client.rpc.RemoteService. Все параметры и возвращаемое значение должны быть упорядочены с помощью GWT. Список типов, отвечающих данному условию, был приведен в предыдущей статье. Однако, одной версии интерфейса сервиса будет недостаточно. Нам также необходимо определить его асинхронную версию, как показано в листинге 3.

Листинг 3. Определение асинхронной версии интерфейса
public interface ToppingServiceAsync {
	public void getAllToppings(AsyncCallback callback);
}

Асинхронная версия сервиса является производной от первичной версии, описанной выше. Обе версии должны быть в одном пакете, который обязан быть видимым для клиентского кода GWT. (Я использовал com.ibm.examples.client). Имя класса в асинхронной версии должно состоять из имени вашего оригинального интерфейса и слова Async, добавленного в конец названия. Для каждого метода в оригинальном интерфейсе, асинхронная версия должна иметь соответствующий метод с возвращаемым значением void, и дополнительным параметром типа AsyncCallback. Код клиентской части использует AsyncCallback для обработки ответа сервера.

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

Еще один момент, и наш код сервера будет готов. Добавьте в файл Slicr.gwt.xml следующую строку:

<servlet path="/toppings" class="com.ibm.examples.server.ToppingServiceImpl"/>

Эта настройка связывает полное имя класса удаленного сервиса со строкой пути, являющегося по сути URL для данного сервиса. Если вы планируете запускать приложение в режиме хоста, вам необходимо всего лишь включить эту строку в данный xml-файл. Однако при развертке приложения в Web, как вы увидите позднее, нам будет необходимо иметь нечто похожее в файле web.xml.

Для работы единственного вызова необходима масса соответствующих вещей - интерфейс, асинхронный интерфейс, фактическая реализация, файл модуля. Отсутствие или несоответствие одной их них приводит к ошибкам при попытке обратиться к сервису. На момент написания статьи, как минимум одна IDE (IntelliJ IDEA) обеспечивает поддержку всех элементов в соответствующем состоянии. Если вы используете Eclipse, плагин Googlipse также обеспечивает аналогичную поддержку.

Создание вызова на стороне клиента.

После завершения работ с серверной частью, нам необходимо реализовать вызов процедуры со стороны клиента. Основная идея состоит в указании системе GWT, какую удаленную службу мы вызываем. Затем мы отсылаем ей объект AsyncCallback; в итоге, GWT возвращает его назад, и мы можем обработать это событие в зависимости от результата. Листинг 2 содержит код настройки и осуществления вызова. Этот метод находится в классе Slicr, использовавшемся в первой статье. Более точно, обращение к данному методу заменяет строку в Slicr.onModuleLoad(), добавляющую панель с наполнителями.

Листинг 4. Настройка и осуществление вызова.
public void callForToppings() {
	ToppingServiceAsync toppingService = 
		(ToppingServiceAsync) GWT.create(ToppingService.class);
	ServiceDefTarget target = (ServiceDefTarget) toppingService;
	String relativeUrl = GWT.getModuleBaseURL() + "toppings";
	target.setServiceEntryPoint(relativeUrl);
	toppingService.getAllToppings(new ToppingCallback());
}

Каждая строка является важным шагом в осуществлении вызова GWT. Разберемся подробнее:

  1. Мы создаем экземпляр асинхронного интерфейса. Как правило, конечно, мы не можем создать экземпляр интерфейса, однако здесь нам поможет вызов GWT.create(). Класс GWT используется во многих случаях; сейчас он позволяет создать объект-заместитель, осуществляющий данный интерфейс. Учтите, что при развертке приложения аргумент данного метода должен быть литералом, а не переменной. Переменные работают только в режиме хоста, так что будьте осторожны.
  2. Объект-заместитель, возвращаемый GWT.create(), также реализует другой интерфейс, ServiceDefTarget. Вкратце, нам необходимы некоторые методы из данного интерфейса.
  3. Вычисление адреса, по которому будет отсылаться сообщение. Он имеет два компонента:
    • Базовый URL системы, который мы получаем с помощью GWT.getModuleBaseURL()
    • Строка, использованная нами в качестве пути при добавлении сервлета в .xml-файл
  4. Имея полный URL, мы можем сообщить GWT, что этот конкретный адрес является местом, по которому необходимо обратиться для работы с определенной службой, при помощи вызова метода setServiceEntryPoint(). Также интерфейс содержит соответствующий get-метод. Теперь наш сервис полностью подготовлен для использования с клиентской страницы.
  5. И, наконец, мы непосредственно обращаемся к сервису определенным ранее образом (кроме объекта ToppingCallback, который будет рассмотрен в следующем разделе).

Нам необходимо инициализировать сервис на клиентской части только один раз (см. первые четыре строки примера). После того, как сервис создан и ассоциирован со своей точкой входа, мы можем использовать тот же объект сервиса для выполнения многочисленных обращений к серверу. Также мы можем создать несколько вспомогательных методов для упрощения шаблонной инициализации серверного объекта.

Преимущество асинхронности

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

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

Интерфейс AsyncCallback определяет два метода: OnSuccess(Object obj) и OnFailure(Throwable t). Вам необходимо создать класс, реализующий оба метода. Вы создаете экземпляр класса и передаете его асинхронному методу сервиса при удаленном вызове, как показано в листинге 4. В итоге, серверная часть работы завершается и вызывается один из двух методов. Признаком успешного выполнения является возвращаемое вызовом значение. В нашем случае, это будет список наполнителей для пиццы. Мы преобразовываем его в необходимый нам тип, и затем делаем что-нибудь полезное с новыми данными. Есть надежда, что GWT будет поддерживать шаблоны Java 1.5 в обозримом будущем, что сможет избавить нас от необходимости многочисленного приведения типов. Если выполнение серверной части кода приводит к исключению, вызывается соответствующий failure-метод с брошенным исключением в качестве аргумента. Так вы можете обработать исключение желаемым образом.

Код, приведенный в листинге 5, содержит класс ToppingCallback, относящийся к листингу 4. Как я, возможно, замечал ранее, вы будете часто видеть классы AsyncCallback, определенные на месте в качестве безымянных. Я рекомендую избегать такого подхода, ведь ваш код будет куда проще читать и отлаживать, если класс имеет имя и не расположен прямо в середине определенного блока кода.

Листинг 5. Класс ToppingCallback
public class ToppingCallback implements AsyncCallback {

	public void onFailure(Throwable caught) {
	    GWT.log("Error ", caught);
		caught.printStackTrace();
	}

	public void onSuccess(Object result) {
		List allToppings = (List) result;
		VerticalPanel toppings = new VerticalPanel();
		toppings.add(new HTML("<h2>Toppings</h2>"));
		Grid topGrid = new Grid(allToppings.size() + 1, 3);
		topGrid.setText(0, 0, "Topping");
		topGrid.setText(0, 1, "Left");
		topGrid.setText(0, 2, "Right");
		for (int i = 0; i < allToppings.size(); i++) {
			Topping t = (Topping) allToppings.get(i);
			Button button = new Button(t.getName() );
			CheckBox leftCheckBox = new CheckBox();
			CheckBox rightCheckBox = new CheckBox();
			clearables.add(leftCheckBox);
			clearables.add(rightCheckBox);
			button.addClickListener(new ToppingButtonListener(leftCheckBox,
					rightCheckBox));
			topGrid.setWidget(i + 1, 0, button);	
			topGrid.setWidget(i + 1, 1, leftCheckBox);
			topGrid.setWidget(i + 1, 2, rightCheckBox);
		}
		toppings.add(topGrid);
		panel.add(toppings, DockPanel.EAST);
	}
	
}

Экземпляр класса ToppingCallback по умолчанию относится к асинхронному интерфейсу. GWT управляет процессом в фоновом режиме, таким образом, соответствующий запрос направляется на сервер, затем ответ от сервера отдается объекту обратного вызова, и, наконец, выполняется соответствующий метод, обрабатывающий ответ.

При такой модели достаточно просто описывать реакцию системы на ошибки. Если работа метода на стороне сервера приводит к исключению по какой-либо причине, контроль при обратном вызове передается методу onFailure(). Его аргументом является исключение или другой перебрасываемый объект, возвращаемый со стороны сервера. Этот аргумент дает возможность должным образом отреагировать на ошибку, если это возможно. Я использую метод GWT.log(), принимающий в качестве аргументов строку и Throwable. Этот метод выводит зарегистрированное сообщение в окно оболочки режима хоста GWT. Однако это работает исключительно в режиме хоста, в Web-режиме этот метод игнорируется. Метод GWT.log() имеет большое значение для отладки во время разработки. Возможно, вы захотите использовать метод для размещения сообщения в части экрана, которую планируется обновить, или для перенаправления всего браузера на экран сообщения об ошибке.

Если вы читали первую статью данной серии, вы должны были заметить, что код onSuccess() почти идентичен методу buildToppingTypePanel() в оригинальной версии. Одно из различий лежит на поверхности - теперь мы перемещаемся по списку, а не массиву, что ведет за собой небольшие изменения. Также теперь на экран выводится цена наполнителя, потому что эта информация доступна из базы данных.

Если копнуть глубже, предыдущая версия возвращала панель toppings вызывавшему ее коду, который добавлял ее на родительскую панель. В данном случае, мы не можем вернуть какое-либо значение, таким образом, нам необходимо добавить созданную панель с начинками на родительскую в рамках данного метода. После запуска, в первую очередь появится панель с пиццей, затем, после небольшой задержки, необходимой GWT для имитации вызова сервера, появится панель начинок, как показано на рисунке 1.

Рисунок 1. Новая панель с наполнителями
Новая панель с наполнителями

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

В следующей статье

В этой статье мы работали над процессом создания RCP с помощью Google Web Toolkit. Таким образом, теперь мы имеем полноценное GWT-приложение. Однако оно все еще работает в режиме хоста на машине разработчика. Для того чтобы сделать его доступным пользователям всемирной сети, нам необходимо узнать больше о Web-режиме работы и развернуть наше Web-приложение в среде разработки в качестве сервлета. В следующей, последней статье данной серии, мы разберем этот вопрос более подробно.

Ресурсы

Научиться

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

Обсудить

Комментарии

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=Open source, Information Management, Технология Java
ArticleID=284906
ArticleTitle=Создание Ajax-проекта с использованием инструментария Google Web Toolkit, Apache Derby и Eclipse : Часть 3. Налаживание связи
publish-date=01302008