[Img_assist | нидь = 3421 | название = | убывание = | ссылка = URL | URL = HTTP: //www.manning.com/affiliate/idevaffiliate.php идентификатор | ALIGN = влево | ширина = 208 | высота = 388] GWT позволяет клиентским приложениям взаимодействовать с ресурсами сервера через собственный RPC и механизм сериализации объектов. Этот коммуникационный процесс включает в себя каждую сторону диалога, реализующую очень простой интерфейс службы GWT и отправляющую / получающую специальные сериализованные данные GWT. Серверная сторона предоставляет ресурсы, а клиентская сторона вызывает эти ресурсы асинхронно.
GWT преднамеренно сохраняет основные вещи и оптимизирует перевод данных из Java в JavaScript и наоборот. Хотя с этим механизмом действительно легко работать и понимать, это GWTism. Другими словами, GWT не использует стандартный или существующий идиоматический подход, в котором не участвуют XML-RPC, SOAP или REST. Но, несмотря на использование своего собственного подхода, GWT также может взаимодействовать с существующими сервисами дополнительными способами, облегчающими интеграцию, такими как использование простого XML через HTTP (POX) или JavaScript Object Notation (JSON). Это дополнительные методы, хотя; нативный механизм взаимодействия клиент-сервер GWT — GWT RPC.
Запуск проекта HelloServer
Мы будем использовать GWT RPC в примере приложения «Hello Server», которое называется HelloServer. Сначала мы определим данные, которые мы хотим передать по проводам, затем создадим конечную точку сервера, в которой описан метод, использующий эти данные, и, наконец, реализуем клиент для завершения процесса. На рисунке 1 показано, как будет выглядеть наше завершенное приложение HelloServer.
[Img_assist | NID = 4706 | название = | убывание = | ссылка = нет | Align = нет | ширина = 252 | Высота = 229]
Рисунок 1. Пример приложения HelloServer, демонстрирующий взаимодействие с сервером GWT. Последняя строка на экране показывает ответ сервера на ввод пользователя.
Мы будем использовать утилиту GWT ApplicationCreator для создания нашего базового проекта, но мы также будем использовать утилиту GWT ProjectCreator для создания файлов проекта Eclipse. После того, как мы используем ApplicationCreator и ProjectCreator, мы создадим пример с использованием Eclipse. Варианты использования ProjectCreator показаны здесь и описаны в таблице 1.
ProjectCreator [-ant projectName] [-eclipse projectName] [-out dir] [-overwrite] [-ignore]
Таблица 1 Параметры ProjectCreator
параметр | Описание |
---|---|
-муравей | Создает файл сборки Ant для компиляции исходного кода (будет добавлен файл .ant.xml) |
-затмение | Создает проект Eclipse |
-вне | Каталог, в который будут записаны выходные файлы (по умолчанию используется текущий каталог) |
-overwrite | Перезаписывает любые существующие файлы |
-ignore | Игнорирует любые существующие файлы; не перезаписывает их |
Давайте начнем с создания нашего приложения с помощью ApplicationCreator, а затем получим нашу конфигурацию проекта Eclipse с помощью ProjectCreator следующим образом:
mkdir [PROJECT_HOME]
cd [PROJECT_HOME]
[GWT_HOME]/ApplicationCreator \
com.manning.gwtip.helloserver.client.HelloServer
[GWT_HOME]/ProjectCreator -eclipse HelloServer
Запуск ApplicationCreator и ProjectCreator, как показано, создаст файлы шаблона проекта по умолчанию и создаст Eclipse-ориентированные файлы .project и .classpath. Оттуда вы можете открыть Eclipse и использовать функцию «Файл»> «Импорт»> «Существующие проекты в рабочую область» для импорта проекта HelloServer. Если у вас есть проект HelloServer в Eclipse Navigator, вы должны увидеть в нем стандартный макет GWT. На рисунке 2 показан импортированный проект HelloServer в перспективе Eclipse Resource.
[Img_assist | NID = 4707 | название = | убывание = | ссылка = нет | Align = нет | ширина = 192 | высота = 160]
Рисунок 2 Макет проекта HelloServer в перспективе Eclipse Resource после того, как ProjectCreator сгенерировал проект Eclipse
Если вы запустите HelloServer-shell, вы вызовете GWTShell и увидите стандартный шаблон «Click Me — Hello World», с которого начинается каждый проект GWT по умолчанию. Создав базовый проект, мы готовы перейти к реализации HelloServer. Мы начнем с определения нашей простой модели данных и рассмотрим механизм сериализации GWT.
Определение сериализуемых данных GWT
Первый класс, который нам нужно создать, — это простой класс данных, который мы назовем Person. Прежде чем наши объекты Person могут быть переданы из клиентского приложения GWT в службу RPC, они должны быть помечены либо с помощью интерфейса com.google.gwt.user.client.rpc.IsSerializable, либо с помощью интерфейса java.io.Serializable. Это концептуально аналогично обычной сериализации Java, но в этом случае используется способом, специфичным для GWT. Также как и при обычной сериализации, GWT учитывает модификатор переходного процесса в свойствах класса, что позволяет их исключать, если они сами не сериализуемы.
Запись
Хотя IsSerializable не определяет какие-либо методы, обязательно, чтобы ваши реализации IsSerializable объявляли конструктор без аргументов.
Интерфейс IsSerializable важен и проблематичен. Это важно, потому что он дает компилятору GWT лучшую информацию о том, какие классы должны поддерживать сериализацию. Это проблематично, поскольку вводит специфическую для GWT зависимость в классы модели, как вы можете видеть в листинге 1, класс Person. Хотя это не является большой проблемой, если вы работаете полностью в рамках своего приложения GWT, оно может быстро прервать сделку, если вы захотите поделиться объектными моделями с другими проектами Java в вашей организации.
Перечисление 1 класс объекта данных Person
package com.manning.gwtip.helloserver.client;
import com.google.gwt.user.client.rpc.IsSerializable;
public class Person implements IsSerializable{
public String name;
public String address;
public Person(){
this(null, null);
}
public Person(String name, String address ) {
super();
this.name = name;
this.address = address;
}
}
Из-за этого GWT-зависимость IsSerializable добавила поддержку GWT 1.4, так что вместо IsSerializable можно использовать java.io.Serializable. Это позволяет этим двум маркерным интерфейсам действовать взаимозаменяемо в контексте GWT. Как правило, это помогает гарантировать, что классы моделей могут быть созданы без прямых зависимостей GWT. Тем не менее, важно понимать, что такие модельные объекты по-прежнему должны быть переведены в GWT. Это означает, что им нужны конструкторы без аргументов, и они не могут использовать возможности языка Java 5 (пока).
Кроме того, важно понимать, что реализация java.io.Serializable в GWT, хотя и удобна, — это просто интерфейс маркера, означающий то же самое, что и IsSerializable, но это не то же самое, что реальная поддержка сериализации Java. Документация для GWT-версии java.io.Serializable объясняет это следующим образом: «открытый интерфейс Serializable: предоставляется для взаимодействия; RPC рассматривает этот интерфейс как синоним IsSerializable. Протокол сериализации Java явно не поддерживается».
Суть в том, что вы можете использовать IsSerializable или Serializable, и они означают то же самое для GWT, этот класс является переводимым RPC. Эмуляция GWT Serializable может помочь вам избежать зависимости IsSerializable от GWT, но классы вашей модели все еще ограничены тем, что возможно с GWT. Из-за этого мы будем использовать маркер IsSerializable, чтобы сделать связь явной. Имейте в виду, однако, что в реальной жизни вам может быть лучше использовать Serializable, если вы достаточно дисциплинированы, чтобы помнить о неявных ограничениях GWT. Мы хотим создать нашу простую сервисную инфраструктуру, чтобы проиллюстрировать основы GWT RPC.
Создание сервисов RPC
GWT включает в себя пакет GWT RPC для обеспечения связи с ресурсами сервера. Создание службы RPC влечет за собой создание двух интерфейсов и реализацию службы.
Вы начинаете с создания синхронного интерфейса службы, который расширяет интерфейс GWT RemoteService и определяет методы, которые предоставляет ваша служба. Далее вы создаете асинхронный интерфейс на основе первого синхронного интерфейса. Этот асинхронный интерфейс будет иметь то же имя, что и синхронный интерфейс, но с суффиксом Async. Важно отметить, что асинхронный интерфейс не расширяет сам RemoteService. Асинхронный интерфейс должен иметь все те же методы, что и синхронный, за исключением того, что каждый из асинхронных методов должен объявлять возвращаемый тип void, не создавать исключений и иметь дополнительный конечный ссылочный параметр типа AsyncCallback. Эти два интерфейса, один синхронный, другой асинхронный, являются клиентской стороной изображения. В заключение,Вы должны создать серверную реализацию синхронного интерфейса RemoteService на стороне клиента. Это должно расширить класс GWT RemoteServiceServlet.
Эти три части, интерфейс службы синхронного клиента, интерфейс службы асинхронного клиента и сервлет службы реализации сервера, являются основой GWT RPC. В таблице 2 эти компоненты RPC приведены для справки.
Таблица 2 Компоненты, участвующие в создании службы GWT RPC
Требуемый интерфейс | расширение | Цель |
---|---|---|
MyService | RemoteService | Сторона клиента. Синхронный интерфейс, используемый внутри GWT. |
MyServiceAsync | Никто | Сторона клиента. Асинхронный интерфейс, который по соглашению поддерживает синхронный интерфейс. Он должен иметь то же имя с суффиксом Async, должен объявлять тип возвращаемого значения void во всех методах, не должен выдавать никаких исключений и должен включать AsyncCallback в качестве последнего параметра во всех методах. |
MyServiceImpl | RemoteServiceServlet | Серверная сторона. Реализация синхронного интерфейса на стороне клиента, который по соглашению будет доступен на клиенте через асинхронный интерфейс. |
В нашем примере мы добавим еще один элемент в микс, чтобы немного отделить нашу реализацию и сделать ее более гибкой. Мы собираемся поместить нашу реализацию RemoteService на стороне сервера в отдельный класс, помимо нашей реализации RemoteServiceServlet. Это может быть сделано за один шаг: у нас может быть реализация на стороне сервера, которая одновременно реализует RemoteService и расширяет RemoteServiceServlet одним махом. Тем не менее, мы разделим эти два в качестве лучшей практики, потому что в более крупном проекте вы можете использовать реализацию сервиса вне контекста ваших классов GWT. Разделив RemoteServiceServlet, вы можете сами реализовать класс интерфейса RemoteService в компоненте Spring, компоненте Enterprise Java Bean (EJB) или даже через службу SOAP.Рисунок 3 усиливает эти моменты, а также показывает структуру, которую мы будем использовать в нашем примере HelloServer.
[Img_assist | NID = 4708 | название = | убывание = | ссылка = нет | Align = нет | ширина = 373 | Высота = 272]
Рисунок 3 Диаграмма классов GWT RPC для HelloServer. Обратите внимание, что класс RemoteServiceAsync не имеет прямого отношения к нашей реализации сервиса или сервлету; это связано по соглашению.
Важно помнить, что все классы, которые вы используете в качестве аргументов или возвращаемых методов, определенных вашим интерфейсом RemoteService, должны быть GWT-сериализуемыми, как мы обсуждали в разделе «Определение сериализуемых данных GWT». Кроме того, ваш удаленный интерфейс и все данные, которые вы хотите сериализовать, должны быть частью вашего исходного пути, чтобы GWTCompiler мог найти эти ресурсы и создать соответствующие версии JavaScript. Приступая к коду RPC, мы начнем с синхронного интерфейса на стороне клиента HelloService.java, который показан в листинге 2.
Перечисление 2 HelloService.java
package com.manning.gwtip.helloserver.client;
import com.google.gwt.user.client.rpc.RemoteService;
public interface HelloService extends RemoteService {
String sayHello(Person p);
}
Далее нам нужно создать наш асинхронный интерфейс на стороне клиента, который практически идентичен синхронному интерфейсу с ранее отмеченными исключениями (суффикс Async, тип возвращаемого значения void, AsyncCallback в качестве параметра, обратный вызов будет использоваться для возврата значения). Обратите внимание, что оба этих клиентских интерфейса находятся в исходном пути по умолчанию, в пакете .client. В листинге 3 показан наш интерфейс HelloServiceAsync.java.
Перечисление 3 HelloServiceAsync.java
package com.manning.gwtip.helloserver.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface HelloServiceAsync {
void sayHello(Person p, AsyncCallback callback);
}
Наконец, нам нужно создать реализацию нашего интерфейса RemoteService на стороне сервера. Реализация нашей службы будет просто старым Java-объектом (POJO), хотя, опять же, на этом этапе вы можете использовать множество различных методов. Этот код для HelloServiceImpl.java показан в листинге 4.
Перечисление 4 HelloServiceImpl.java
package com.manning.gwtip.helloserver.server;
import com.manning.gwtip.helloserver.client.HelloService;
import com.manning.gwtip.helloserver.client.Person;
public class HelloServiceImpl implements HelloService {
public HelloServiceImpl() {
}
public String sayHello(Person p) {
return "Hello " + p.name + ". How is the weather at " + p.address +
"?";
}
}
Обратите внимание, что даже несмотря на то, что мы создали два клиентских интерфейса, один синхронный и один асинхронный, GWT не поддерживает синхронную связь с вашим клиентским приложением. Вы никогда не будете использовать синхронный. Причина этого связана с браузером и техническая, и вы, возможно, знакомы с ним, если вы уже работали с Ajax в прошлом. Объект XMLHttpRequest является асинхронным; однако интерпретатор JavaScript во многих браузерах является одним потоком выполнения. Это означает, что если вы попытаетесь отправить запрос и «прокрутить», ожидая выполнения события обратного вызова, вы будете вращаться вечно. Событие обратного вызова не сработает, пока работает интерпретатор JavaScript.
Теперь у нас есть три класса, но мы еще не закончили. Последняя часть — наша реализация RemoteServiceServlet. Это фактический сервлет, предоставляемый веб-приложением и с которым взаимодействуют клиентские классы. В листинге 5 показан код нашего сервисного сервлета HelloServlet.java.
Перечисление 5 HelloServlet.java
package com.manning.gwtip.helloserver.server;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.manning.gwtip.helloserver.client.HelloService;
import com.manning.gwtip.helloserver.client.Person;
public class HelloServlet
extends RemoteServiceServlet
implements HelloService {
private HelloService impl = new HelloServiceImpl();
public HelloServlet() {
super();
}
public String sayHello(Person p) {
return impl.sayHello(p);
}
}
Это завершает наш сервис: два простых интерфейса на клиенте, один класс реализации на сервере и сервисный сервлет для размещения и вызова реализации на стороне сервера. Теперь, когда у нас есть базовый сервис, мы рассмотрим варианты, доступные в классе RemoteServiceServlet.
Расширение на RemoteServiceServlet
Пока мы оборачиваем нашу реализацию, в спецификации сервлета есть некоторые функции, которые могут нам понадобиться. Действительно, мы могли бы захотеть сделать много вещей с запросом, прежде чем он перейдет к фактической реализации сервиса. RemoteServiceServlet включает несколько методов, которые вы можете вызывать из своего сервлета для доступа к этим функциям. Важные из них изложены в таблице 3.
Таблица 3 Избранные методы RemoteServiceServlet
метод | функция |
---|---|
getThreadLocalRequest () | Вызывается из метода службы для получения объектов HttpServletRequest и HttpSession. Он может использоваться для состояния на стороне сервера с сеансом или настройкой ответов. |
getThreadLocalResponse () | Вызывается из метода службы для получения объекта HttpServletResponse. Его можно использовать для настройки ответа, например, для настройки пользовательских заголовков. |
onBeforeRequestDeserialized (String) | Вызывается до десериализации объектов запроса с полезной нагрузкой сериализации в качестве аргумента. |
onAfterResponseSerialized (String) | Вызывается до того, как сериализованный объект ответа возвращается клиенту с сериализованной полезной нагрузкой в качестве аргумента. |
shouldCompressResponse (HttpServletRequest, HttpServletResponse, String) | Вызывается, чтобы определить, следует ли использовать сжатие Gzip в ответе. Поведение по умолчанию — true, если клиент принимает его, а ответ превышает 256 байтов. |
processCall (String) | Вызывается для десериализации входящих полезных нагрузок, вызова соответствующего метода обслуживания и возврата полезной нагрузки строкового ответа. |
Наиболее важными из методов в таблице 3 являются два метода ThreadLocal, которые позволяют получить доступ к состоянию сеанса. Например, если вы передавали вызовы в службу RPC в службу SOAP, вы можете проверить сеанс и аутентифицировать пользователя с помощью службы SOAP при первом вызове, используя информацию о пользователе, доступную в объекте HttpServletRequest. Оттуда вы можете сохранить заглушку соединения для пользователя в объекте Session, предоставив каждому пользователю собственный экземпляр фактического бизнес-объекта.
НОТА
Мы пропускаем сервис безопасности вообще, ради простоты. Однако следует помнить, что старая пила «никогда не доверяй клиенту» по-прежнему применима.
Вам могут не понадобиться другие методы, перечисленные в таблице 3, но они могут быть полезны. Важно помнить, что классы сериализации Google доступны для вас в вашем приложении в виде com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader и ServerSerializationStreamWriter. Если, например, вы хотите изменить состояние службы на основе информации, содержащейся в запросе клиента, вы можете перегрузить методы в таблице 3 и предварительно проверить сообщение на сервере. К сожалению, эти методы не позволяют вам фильтровать или изменять полезные данные сериализации. Для этого вам нужно перегрузить весь метод processCall (), а затем внести изменения перед вызовом метода суперкласса. Все это происходит на сервере, но мы также хотим совершать звонки от клиента.
Вызов сервера с клиента
Чтобы вызывать наши RPC-сервисы на клиенте, нам нужно получить интерфейс асинхронного сервиса из статического метода GWT.create (), связать его с относительным URL-адресом для нашего HelloServlet, создать обработчик обратного вызова и выполнить наш вызов. В листинге 6 это демонстрируется непосредственно в классе EntryPoint HelloServer. Этот класс, который сейчас входит в наш проект Eclipse, изначально был создан утилитой ApplicationCreator. Мы полностью заменили то, что было в файле HelloServer.java по умолчанию, на наш код. (Для целей этого примера мы делаем вещи непосредственно в точке входа. Мы не пытаемся развиваться с учетом повторного использования.)
Перечисление 6 HelloServer.java
public class HelloServer implements com.google.gwt.core.client.EntryPoint {
private HelloServiceAsync service;
private TextBox name = new TextBox();
private TextBox address = new TextBox();
private Label response = new Label();
private AsyncCallback serviceCallback =
new AsyncCallback() {
public void onSuccess(Object result) {
String string = (String) result;
response.setText(string);
}
public void onFailure(Throwable caught) {
Window.alert("There was an error: " + caught.toString());
}
};
public HelloServer() {
super();
}
public void onModuleLoad() {
service = (HelloServiceAsync)
GWT.create(HelloService.class);
ServiceDefTarget endpoint = (ServiceDefTarget) service;
endpoint.setServiceEntryPoint(
GWT.getModuleBaseURL() +
"/HelloService");
RootPanel root = RootPanel.get();
root.add(new Label("Name"));
root.add(name);
root.add(new Label("Address"));
root.add(address);
Button button = new Button("Hello!",
new ClickListener() {
public void onClick(Widget sender) {
service.sayHello(new Person(name.getText(),
address.getText()), serviceCallback);
}
});
root.add(button);
root.add(response);
}
}
Наша точка входа HelloServer предназначена для соединения нашего примера с пользовательским интерфейсом и обработкой событий. В нем мы создаем объект AsyncCallback для обработки возвращаемых значений из нашего сервиса. (Это также можно было бы сделать анонимно.) Затем мы используем статический метод GWT.create (), чтобы получить ссылку во время выполнения на наш сервис, и реализуем ClickListener, чтобы связать нажатие кнопки с вызовом службы sayHello ().
Последний шаг, который нам нужно выполнить, — это настроить наш файл модуля HelloServer.gwt.xml, в котором наш сервлет сопоставлен с / HelloService. Если мы посмотрим на существующий файл HelloServer.gwt.xml, созданный ApplicationCreator, мы увидим один
элемент и один
элемент класса. Нам нужно добавить к этому
элемент для представления нашего сервиса, как показано в листинге 7.
Модуль перечисления 7 HelloServer.gwt.xml с добавленной записью сервлета
<module>
<inherits name='com.google.gwt.user.User'/>
<entry-point
class='com.manning.gwtip.helloserver.client.HelloServer'/>
<servlet path="/HelloService"
class=
"com.manning.gwtip.helloserver.server.HelloServlet"/>
</module>
Теперь у нас есть полноценное работающее приложение GWT, которое перезванивает на сервер! Хотя наш пример не впечатляет визуально, теперь вы должны быть знакомы со всеми движущимися частями, участвующими в асинхронном вызове кода на стороне сервера.
Чтобы запустить этот пример в размещенном режиме, вы можете просто вызвать GWTShell с помощью сценария ярлыка HelloServer-shell. (Этот сценарий был либо создан при запуске ApplicationCreator вручную, если вы следили за ним и строили проект, либо снабжен кодом для этого примера на веб-сайте Manning.) Когда вы запустите пример, клиент вызовет сервер, и под капотом объекты Java преобразуются в проводной формат GWT и передаются в JavaScript. На рис. 4 представлен визуальный обзор всего процесса RPC: от пользователя до интерфейса службы клиента, через провод в сервлете удаленной службы, а затем до реализации службы, а затем снова обратно.
[Img_assist | NID = 4709 | название = | убывание = | ссылка = нет | Align = нет | ширина = 373 | Высота = 260]
Рисунок 4 Обзор полного процесса RPC. Обратите внимание, что линия жизни браузера пользователя освобождается во время выполнения вызова службы; он снова монополизирован при возврате вызова.
На рисунке 5 показан пример запроса в шестнадцатеричном формате, чтобы вы могли видеть специальные символы, используемые во время вызова сервисного вызова. Хотя для вас не важно иметь полное представление о шестнадцатеричных значениях, это демонстрирует важный момент о работе компилятора по отношению к серверу.
[Img_assist | NID = 4710 | название = | убывание = | ссылка = нет | Align = нет | ширина = 585 | Высота = 497]
Рисунок 5 Запрос GWT RPC в шестнадцатеричном формате. Обратите внимание, что информация о типе передается для десериализации на сервер вместе со значениями атрибута.
Как видно на рисунке 5, имя класса — это важная информация, используемая при передаче, за которой следуют свойства и последовательность, чтобы определить, какие значения принадлежат какому свойству. В GWT (и в отличие от JSON) имена свойств объектов могут меняться от компиляции к компиляции, но клиент GWT всегда будет знать имя класса Java объекта. Теперь для ответа:
{OK}[1,["Hello John Doe. How is the weather at Anytown, NA, 5555"],0,2]
Во-первых, у нас есть код ответа на вызов к серверу, а затем наше единственное возвращаемое значение. Поскольку мы возвращаем простое строковое значение, оно возвращается в собственной форме JavaScript. После возвращения значений они будут часто обновлять уровень модели вашего Ajax-приложения.
Устранение неполадок связи с сервером
Если вы начинаете получать ошибки в виде «Тип результата отложенного связывания« module.client.MyService »не создается», когда вы начинаете создавать службы GWT RPC, попробуйте следующее. Во-первых, включите опцию -logLevel на консоли ведения журнала оболочки, чтобы получить дополнительные подсказки о том, что пошло не так. Затем выполните этот контрольный список:
- Убедитесь, что вы используете GWT.create () для MyServiceAsync, а не для MyService.
- Убедитесь, что ваш интерфейс MyService расширяет RemoteService.
- Убедитесь, что все возвращаемые типы и аргументы реализуют IsSerializable или Serializable.
- Убедитесь, что классы, используемые в качестве возвращаемых типов и аргументов, имеют конструкторы без аргументов.
- Убедитесь, что все возвращаемые типы методов в вашем классе MyServiceAsync являются недействительными.
Помимо связи с сервером GWT и проблем, связанных с именами и типами, вам также может быть интересно работать с синхронизацией и множеством ожидающих обратных вызовов. Разработчики, знакомые с проблемами, связанными с асинхронным программированием на основе сообщений, возможно, рассмотрят асинхронную природу обмена сообщениями Ajax и рассмотрят проблемы синхронизации на уровне модели в своих приложениях GWT.
Важно помнить, что весь JavaScript на странице выполняется в рамках одного потока. Это означает, что, хотя у вас может быть несколько ожидающих вызовов, ожидающих вызова, одновременно будет вызываться только один. В отличие от вызова веб-служб через Java API для веб-служб XML (JAX-WS) из приложения Swing, нет необходимости переносить изменения пользовательского интерфейса из потока, вызывающего обратный вызов, в поток рисования, поскольку весь JavaScript выполняется в потоке рисования , Чтобы позаимствовать аналогию у Брайана Глика (http://www.jroller.com/hifi78/entry/gwt_single_threaded_javascript_multi):
С однопоточным браузером я думаю о пчелиной колонии. (ПРЕДУПРЕЖДЕНИЕ: Предельно напряженная метафора!) Пчелиная матка (приложение) может сказать рабочим пчелам (XMLHttpRequest): «Идите, принесите мне еду». Однако, когда пчелы возвращаются, только одна может дать еду королеве за раз. Остальные должны сидеть в очереди и ждать. С точки зрения пчеловода, среда многопоточная. Все эти пчелы роятся одновременно. Тем не менее, мы должны смотреть на это с точки зрения королевы, которая имеет дело только с одной рабочей пчелой за раз.
То есть проблемы, обычно связанные с многопоточным программированием в Java, не применяются. Ваша Java всегда будет выполняться в одном потоке. Вы никогда не будете иметь несколько обратных вызовов, выполняющихся на клиенте одновременно. Атрибуты никогда не будут изменены вне текущего стека вызовов. В отличие от стандартного Servlet API, ваш код GWT на стороне клиента будет однопоточным во всех экземплярах, которые вы создаете в коде; это будет принципиально статично. Нет потока рассылки событий Swing. Там нет таймеров демонов. Существует один поток выполнения, в котором будет выполняться весь код, независимо от порядка обратного вызова.