Когда я увидел gwt и gwt-ext, я подумал, что меня где-то обманули, когда не рассказали об этом раньше. Мучения с отладкой скриптов с использованием ExtJS были долгими, мы использовали Java как серверную платформу, вручную занимались сериазилацией/десереализацией серверных объектов, подгоняли блоки с помощью css и занимались многими другими вещами, отнимавшими кучу времени. Однако, можно все это оставить позади. Теперь можно рисовать красивые экстовые окошки кодом на Java (not js)! GWT — замечательная вещь. Она позволяет нам уйти от написания js-кода, потому что генерирует js-код самостоятельно; и программист может даже его не смотреть, потому что отлаживать его можно тоже в исходниках на Java!Далее я постараюсь рассказать, как настроить gwt по�� netbeans.

Ставим на Netbeans плагин для GWT:
При этом для клиента создан следующий код:
Еще нужно не забыть кинуть ExtJS 2.0.2 в папку web/js/ext. И в довершение, нужно рассказать модулю, что у него появились новые компонент, связанные c extJs. Для этого откроем файла our/sample/Face.gwt.xml и поправим его следующим образом:
Замечу только, что для того, чтоб собрать war-файл для веб-сервера, нужно исключить из библиотек проекта gwt-dev-windows.jar.
И, на последок, можно позволить себе немного самокритики и замечаний.
upd: чтобы все это не казалось пустым и ненужным, можно посмотреть примеры на gwt-ext.
Приступаем
Итак, целью является создание веб-приложения на java. Не мудрствуя лукаво, интерфейс повесим на extJS, серверную логику — на java, а с бд будем работать с помощью hibernate.Получение компонентовНам понадобятся следующие компоненты:- Netbeans 6.5 Java или All (для Windows 212 и 249 мб соответсвенно, для других ОС размер не должен сильно отличаться);
- ExtJs версии 2.0.2, и не старше;
- Google Web Toolkit 1.5;
- hibernate core;
- gwt-ext 2.0.5.

Необходимые опции при установке Netbeans.
Ставим на Netbeans плагин для GWT:
- открываем меню Tools->Plugins;
- открываем вкладку Available Plugins;
- ищем плагин Gwt4Nb (можно воспользоваться поиском).
Голый GWT
После этого создаем новый web-проект (File -> New project -> Java web -> Web application). Проекту нужно дать имя и указать место, где он будет сохраняться. В качестве сервера указываем Tomcat (или по предочтению — другой). На последней странице указываются фреймворки, необходимые для нашего проекта. Включаем Google Web Toolkit и Hibernate. При этом для GWT необходимо выбрать директорию, куда мы распаковали gwt. Я выбрал для этого папку $ProjectPath/lib/gwt. Также нужно указать имя модуля GWT, который мы будем разрабатывать. Пусть будет our.sample.Face. Также нужно настроить подключение hibernate, я не буду описывать этот процесс, про него много уже было сказано, например, можно посмотреть статью уважаемого хабрачеловека garbuz. Далее нажимаем Finish, и можно продолжать.Сразу рекомендую выставить для среды исполнения опцию, увеличивающию ограничения на память для исполнения. Это делается в свойствах проекта, Run -> VM Options выставить в "-Xmx512m". Также необходимо увеличить максимальную память для компилятора, и здесь прийдется пойти на небольшую хитрость — нужно открыть файл netbeans/build-gwt.xml и изменить его так, чтобы он выглядел следующим образом:....<br/><java classpath="${javac.classpath}:${src.dir}" failonerror="true"<br/> classname="com.google.gwt.dev.GWTCompiler" fork="true"><br/> <arg value="-out"/><br/> <arg path="${build.web.dir}/"/><br/> <arg value="-style"/><br/> <arg value="${gwt.compiler.output.style}"/> <arg value="-logLevel"/><br/> <arg value="${gwt.compiler.logLevel}"/><br/> <arg value="${gwt.module}"/><br/> <jvmarg value="-Xmx512m" /><br/></java><br/>....<target name="debug-connect-gwt-shell" if="netbeans.home" depends="init">http://bankinform.ru/habraeditor/images/yu-logo.png<br/>... <br/> <java fork="true" classname="com.google.gwt.dev.GWTShell" failonerror="true"><br/> <jvmarg value="-Xmx512m" /><br/> ....<br/> </java><br/></target><br/>....<br/><br/>* This source code was highlighted with Source Code Highlighter.Среда создала пакеты our.sample.client и our.sample.server. Прошу любить и жаловать — в этих директориях будет лежать соответственно клиентский и серверный код. Если быть более точным, на сервере после компиляции остают��я классы из пакета client, однако на клиент классы из сервера не попадают. Тем не менее, наща софтинка пока не готова к запуску. Далее можно запустить проект, чтобы увидеть модуль, который создается по умолчанию. Для каждого gwt-модуля указывается точка входа, класс — который является некоторым аналогом класса с методом main(), и начинает работу системы на клиенте. Именно там начинается отрисовка первой формы, потом из нее обычно бывает вызов второй и т.д. Все, как у людей :). Точка входа по умолчанию — класс $ModuleName + EntryPoint, в нашем случае — our.sample.FaceEntryPoint.Нажамаем кнопку Debug (нам предложат выбор: серверную или клиентсткую отладку мы хотим запустить) и потерпеть, пока соберется приложение. Затем видим наш семпл запущенным:
Пробный запуск.
При этом для клиента создан следующий код:
package our.sample.client;<br/><br/>import com.google.gwt.core.client.EntryPoint;<br/>import com.google.gwt.user.client.ui.Button;<br/>import com.google.gwt.user.client.ui.ClickListener;<br/>import com.google.gwt.user.client.ui.Label;<br/>import com.google.gwt.user.client.ui.RootPanel;<br/>import com.google.gwt.user.client.ui.Widget;<br/><br/><br/>public class MainEntryPoint implements EntryPoint {<br/> // перегруженная функция загрузки модуля<br/> public void onModuleLoad() {<br/> // создаем обычную текстовую метку<br/> final Label label = new Label("Hello, GWT!!!");<br/> // создаем кнопку<br/> final Button button = new Button("Click me!");<br/> <br/> // вешаем на кнопку обработчик, делающий метку невидимой.<br/> button.addClickListener(new ClickListener(){<br/> public void onClick(Widget w) {<br/> label.setVisible(!label.isVisible());<br/> }<br/> });<br/> <br/> // добавляем на "корневую" панель нопку и метку.<br/> RootPanel.get().add(button);<br/> RootPanel.get().add(label);<br/> }<br/>}<br/><br/>* This source code was highlighted with Source Code Highlighter.Не думаю, что здесь что-то нуждается в объяснении. GWT-Ext>
Далее нужно подключить саму библиотеку gwt-ext, потому что голые стандартные веб-компоненты нас не устроят. Для этого открываем свойства проекта (контекстное меню Properties при правом клике на корень дерева проекта), далее в Libraries -> Add Library -> Create; далее вводим имя: «gwt-ext», а в качестве классов (вкладка Сlasspath) и исходников (вкладка Sources) указываем файл gwtext.jar из архива Gwt-ext. Далее нажимаем Add Library.
Подключение библиотеки gwt-ext.
Еще нужно не забыть кинуть ExtJS 2.0.2 в папку web/js/ext. И в довершение, нужно рассказать модулю, что у него появились новые компонент, связанные c extJs. Для этого откроем файла our/sample/Face.gwt.xml и поправим его следующим образом:
<?xml version="1.0" encoding="UTF-8"?><br/><module><br/> <inherits name="com.google.gwt.user.User"/><br/> <inherits name='com.gwtext.GwtExt'/><br/> <entry-point class="our.sample.client.FaceEntryPoint"/><br/><br/> <stylesheet src="js/ext/resources/css/ext-all.css"/><br/> <script src="js/ext/adapter/ext/ext-base.js"/><br/> <script src="js/ext/ext-all.js"/><br/></module><br/><br/>* This source code was highlighted with Source Code Highlighter.А теперь попробуем написать что-то осмысленное. Добавим класс Person, содержащий поля с некоторыми сведениями о человеке, и попробуем на клиенте отрисовать данные, загруженные сервером в этот класс. В данном случае я хочу показать, как работаю асинхронные сервисы (GWT RPC). Нажимаем правой кнопкой на пакет our.sample.client, и добавляем класс Person:package our.sample.client;<br/><br/>import com.google.gwt.user.client.rpc.IsSerializable;<br/><br/>// чтобы класс мог быть сериализован в Person, он должен реализовывать<br/>// интерфейс IsSerializable, хотя никаких методов он не содержит.<br/>public class Person implements IsSerializable {<br/> private String name;<br/> private String surname;<br/> private String patronymic;<br/> private String email;<br/> private int age;<br/> // ....<br/> // далее геттеры-сеттеры для всех этих полей<br/> // ....<br/>}<br/><br/>* This source code was highlighted with Source Code Highlighter.Далее создаем наш сервис, который будет отдавать клиенту экземпляр Person по запросу. Для этого нажимаем правой кнопкой на пакет our.sample.client, выбираем new->other, выбираем категорию Google Web Toolkit и в ней — GWT RPC Service. Необходимо ввести имя нового сервиса, и если нужно — высатвить галочку «Создать пример использования». При этом создается два интерфейса на стороне клиента, класс на стороне сервера и, если была установлена галочка — клиентский класс-entry point с примером использования сервиса. Введем имя сервиса PersonService. Добавим в PersonService.java код, чтобы в итоге класс выглядел так:package our.sample.client;<br/><br/>import com.google.gwt.user.client.rpc.ServiceDefTarget;<br/>import com.google.gwt.user.client.rpc.RemoteService;<br/>import com.google.gwt.core.client.GWT;<br/>public interface PersonService extends RemoteService{<br/> public Person loadPerson(int someValue);<br/> <br/> public static class App {<br/> private static final PersonServiceAsync ourInstance;<br/> static {<br/> ourInstance = (PersonServiceAsync) GWT.create(PersonService.class);<br/> ((ServiceDefTarget) ourInstance).setServiceEntryPoint(GWT.getModuleBaseURL() + "PersonService");<br/> }<br/> public static PersonServiceAsync getInstance()<br/> {<br/> return ourInstance;<br/> }<br/> }<br/>}<br/><br/>* This source code was highlighted with Source Code Highlighter.Вложенный класс App позволяет получить экземпляр сервиса, и вообще непонятно, почему среда не создает его сама, зато впихивает его аналог в пример использования.Так что можно генерировать его, и копировать в нужное место. После этого среда покажет ошибку, мол синхронная и асинхронная версии интерфейса сервиса рассинхронизировализсь, и предложит это поправить, если вы нажмете alt+enter, поставим текстовый курсор в место в ошибкой.Код интерфейса PersonServiceAsync все равно нужно будет поправить руками следующим образом:package our.sample.client;<br/>import com.google.gwt.user.client.rpc.AsyncCallback;<br/><br/>public interface PersonServiceAsync {<br/> public abstract void loadPerson(int someValue, AsyncCallback<Person> asyncCallback);<br/>}<br/><br/><br/>* This source code was highlighted with Source Code Highlighter.И в пакете our.sample.server сделаем реализацию:package our.sample.server;<br/>import com.google.gwt.user.server.rpc.RemoteServiceServlet;<br/>import our.sample.client.Person;<br/>import our.sample.client.PersonService;<br/><br/>public class PersonServiceImpl extends RemoteServiceServlet implements PersonService {<br/> <br/> public Person loadPerson(int someValue) {<br/> Person person = new Person();<br/> person.setSurname("Some");<br/> person.setName("Usual");<br/> person.setPatronymic("Man");<br/> person.setAge(someValue);<br/> person.setEmail("man@domain.com");<br/> return person;<br/> }<br/>}<br/><br/>* This source code was highlighted with Source Code Highlighter.К сожалению, плагин gwt4nb не совсем адекватно воспринимают методы интефейсов, возвращающих какой-то результат. Допустим, наш сервис должен вернуть экземпляр Person, и gwt4nb предложит создать для асинхронного варианта интерфейса метод, возвращающий Person. Однако, это неправильно, т.к. все результаты (а также сообщения об ошибках) получаются с помощью последнего параметра асинхронного метода — экземпляра класса, реализующего интерфейс AsyncCallback. На самом деле, чтобы метод возвращал какой-то объект, нужно описать метод public abstract void loadPerson(int someValue, AsyncCallback asyncCallback). Здесь первый параметр — входной, а AsyncCallback должен обеспечивать обработку результата выполнения метода на сервере. Для того, чтобы он мог вернуть объект определенного класса, нужно подставить в шаблон этот класс (в нашем случае — Person). Как это использовать, мы увидим ниже.package our.sample.client;<br/><br/>import com.google.gwt.core.client.EntryPoint;<br/>import com.google.gwt.user.client.rpc.AsyncCallback;<br/>import com.google.gwt.user.client.ui.RootPanel;<br/>import com.gwtext.client.core.EventObject;<br/>import com.gwtext.client.core.Position;<br/>import com.gwtext.client.widgets.Button;<br/>import com.gwtext.client.widgets.MessageBox;<br/>import com.gwtext.client.widgets.Panel;<br/>import com.gwtext.client.widgets.event.ButtonListenerAdapter;<br/>import com.gwtext.client.widgets.form.*;<br/><br/><br/>public class FaceEntryPoint implements EntryPoint {<br/><br/> public FaceEntryPoint() {<br/> }<br/><br/> final static TextField txtName = new TextField("Фамилия", "name", 190);<br/> final static TextField txtSurname = new TextField("Имя", "surname", 190);<br/> final static TextField txtPatronymic = new TextField("Отчество", "patronymic", 190);<br/> final static TextField txtEmail = new TextField("Email", "email", 190);<br/> final static TextField txtAge = new TextField("Возвраст", "age", 190);<br/><br/> public void onModuleLoad() {<br/> Panel panel = new Panel();<br/> panel.setBorder(false);<br/> panel.setPaddings(15);<br/><br/> final FormPanel formPanel = new FormPanel(Position.CENTER);<br/> formPanel.setFrame(true);<br/> formPanel.setTitle("Тестовая формочка");<br/> formPanel.setWidth(500);<br/> formPanel.setLabelWidth(100);<br/><br/> FieldSet fieldSet = new FieldSet("Человек");<br/> fieldSet.add(txtName);<br/> fieldSet.add(txtSurname);<br/> fieldSet.add(txtPatronymic);<br/> fieldSet.add(txtEmail);<br/> fieldSet.add(txtAge);<br/> txtEmail.setVtype(VType.EMAIL);<br/> txtAge.setVtype(VType.ALPHANUM);<br/><br/> fieldSet.setWidth(formPanel.getWidth() - 20);<br/> final Button btnLoad = new Button("Загрузить", new ButtonListenerAdapter() {<br/> public void onClick(Button button, EventObject e) {<br/> PersonService.App.getInstance().loadPerson(29, new AsyncCallback<Person>() {<br/><br/> public void onFailure(Throwable caught) {<br/> MessageBox.alert("Извините, произошла какая-то ошибка!");<br/> }<br/><br/> public void onSuccess(Person result) {<br/> txtName.setValue(result.getName());<br/> txtSurname.setValue(result.getSurname());<br/> txtPatronymic.setValue(result.getPatronymic());<br/> txtAge.setValue(String.valueOf(result.getAge()));<br/> txtEmail.setValue(result.getEmail());<br/> }<br/> });<br/> }<br/> });<br/> formPanel.add(fieldSet);<br/> formPanel.addButton(btnLoad);<br/> panel.add(formPanel);<br/> RootPanel.get().add(panel);<br/> }<br/>}<br/><br/>* This source code was highlighted with Source Code Highlighter.В коде происходит следующее: - в классе объявляются поля, и объявляются они статическими. Почему, увидим чуть ниже;
- создаем и настраиваем набор полей, располагаем их на панелях, настраиваем их поведение и отображение;
- на кнопку вешаем обработчки события onClick; обработчик представляет собой анонимный класс, реализующий интерфейс ButtonListenerAdapter. Поскольку это отдельный класс, а в нем нам нужно работать с полями из класса FaceEntryPoint, то мы объявили эти поля статическими.
- внутри обработчика нажатия на кнопку находится вызов нашего замечаетльного сервиса. Сервис также представляет собой реализацию интерфейса, на этот раз — AsyncCallback. При успешном выполнении вызывается метод onSuccess, а при ошибке — onFailure. Параметры onSuccess опреляются параметрами шаблона.

Замечу только, что для того, чтоб собрать war-файл для веб-сервера, нужно исключить из библиотек проекта gwt-dev-windows.jar.
И, на последок, можно позволить себе немного самокритики и замечаний.
- плагин gwt4nb несколько сыроват, что видно из ухищрений, к которым приходилось прибегать.
- уже вышел gwt 1.6 m1, статья же ориентирована на gwt 1.5.
upd: чтобы все это не казалось пустым и ненужным, можно посмотреть примеры на gwt-ext.