Java вместо javascript (gwt+netbeans)

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

    Приступаем

    Итак, целью является создание веб-приложения на java. Не мудрствуя лукаво, интерфейс повесим на extJS, серверную логику — на java, а с бд будем работать с помощью hibernate.Получение компонентовНам понадобятся следующие компоненты:
    1. Netbeans 6.5 Java или All (для Windows 212 и 249 мб соответсвенно, для других ОС размер не должен сильно отличаться);
    2. ExtJs версии 2.0.2, и не старше;
    3. Google Web Toolkit 1.5;
    4. hibernate core;
    5. gwt-ext 2.0.5.
    Далее, выкачав все необходимое ставим Netbeans, не забыв включить поддержку web-разработки и отметить галочкой установку Tomcat, на котором мы будем запускать все наше добро.
    Необходимые опции при установке 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.
    Я не пробовал использовать gwt в eclipse, вполне вероятно, что плагины для него более цельные и функциональные. А вот поддержка в IntelliJ IDEA 8 порадовала.Скорее, нужно было упорядочить знания по использования gwt, и статья писалась скорее для себя. Буду рад, если кому то окажется полезным.

    upd: чтобы все это не казалось пустым и ненужным, можно посмотреть примеры на gwt-ext.
    Share post

    Comments 24

      0
      устал крутить колесико.
        0
        приделал кат.
          0
          Ведь та же самая функция реализована в JSF\IceFaces — автогенерация js-скриптов на форме по java-коду… А вот про GWT я как раз не слышал…
            +1
            ну не скажите — у jsf и gwt концепции всё-таки разные…
              +1
              IceFaces делает тяжеленный код на стороне клиента + на каждый чих пользователя обращается на сервер.
              Т.е. поведение самого интерфейса правильней реализовывать полностью на стороне клиента, а с IceFaces это не так.
                0
                Кстати, интересно, GWT поступает так же, или всё-таки нет?
                  0
                  gwt переносит практически весь возможный код на клиентскую сторону+генерит «только» js
                    0
                    О, вот это действительно интересно.

                    А покажите пожалуйста клиентский код, которой GWT сгенерил для Вашего примера «Hello, GWT!!!».
                      0
                      Кода было сгенерировано 6.1 кб. Это, пожалуй не так уж мало, и с вашего позволения, я не буду постить его сюда.
                      Если есть желание, могу отправить мылом.
                        +1
                        Вы можете закинуть его на Сodepad.org или Pastie.org и дать ссылочку всем.
                          0
                          спасибо, буду знать :)
                          Код представляет собой следующее:

                          Только он обфусцированный, осторожнее!
                          pastie.org/389001
                        0
                        Кстати, опять же, можно, вооружившись firebug'ом, посмотреть примеры на сайте gwt-ext. Там, пожалуй, посерьезнее будут примеры =)
                  0
                  Здесь, как можно заметить, используется ext-js. Это не просто ajax на автосгенерированных скриптах, это все таки сильно =)
                  0
                  Нет, тут большая часть кода работает на клиенте именно так, как это написано в коде.
                  Google переписали часть jre, и его ограниченная часть практически портирована в js. Конечно, за один раз подключается не все сразу!
                    0
                    а просветите, пожалуйста, что у ext'а сейчас с лицензией?
                    раньше, кажется, была lgpl+commercial, а потом они перешли на «плохой» gpl+commercial, после чего популярности у них поубавилось.
                      0
                      Я так понимаю, имелся ввиду extjs. Лицензия extjs — GPL, это правда.
                      Я думаю, это не имеет значения в случае в js-кодом, т.к. он все равно полностью попадает на клиент, Вы не сможете его скрыть.
                      При некоторых усилиях вполне возможно представить любое ajax-приложение как комплекс двух частей — клиентской и серверной :)
                        0
                        >При некоторых усилиях вполне возможно представить любое ajax-приложение как комплекс двух частей — клиентской и серверной :)
                        боюсь, что не получится, иначе б они и не переходили на gpl:
                        extjs.com/products/license.php
                      0
                      Эх… Ext-Js конечно красивый, но конкретно GWT-Ext — помирающее глюкало.
                      Шаг в сторону — ошибка где-то в недрах JavaScript без какой-либо диагностики. То есть совсем никак невозможно понять, что случилось и как это исправить. Можно только методом тыка обтачивать примеры.
                      JavaDoc к GWT-Ext написать видимо должен Пушкин.
                      Исходный автор уже давно срулил писать SmartGWT. А те что остались вместо него, на мои письма гордо молчат.

                      Потратил месяц на написание поддержки для GWT-Ext в нашем WYSIWYG GUI builder-е для GWT.
                      Жалею. :-(

                      Эх, хоть бы Ext-GWT оказался лучше…
                        0
                        Модуль gwt в IntelliJ Idea позволяет настраивать выходной js-код — obfuscated, pretty, detailed. Там это делается с помощью UI, хотя можно просто прописать флаг "-style" для GWT-компилятора. Затем можно запустить скомпиленный war-файла в контейнере сервлетов — и вот вам классическая отладка в firebug. Или еще проще — кнопка compile/browse во встроенном отладчике gwt позволяет тем же FF открывать страницы при отладке.
                          0
                          Про компиляцию я в курсе, GWT Designer в Eclipse тоже это умеет. ;-)
                          Но все это не делает GWT-Ext лучше, оно по-прежнему глюкало.

                          И вообще, сначала рекламировать Java вместо JavaScript, а потом оказывается, что таки нужно в JavaScript погружаться. Не-е-е, такие библотеки нам не нужны.

                          Собственно GWT-Ext сам можно и в Eclipse отлаживать, в hosted mode как Java приложение. Только там в 90% случаев — нырок в JavaScript и переныривание в Ext-Js. А сам Ext-Js есть уже в виде debug, так что все равно, как компилировать GWT код. ;-)
                        0
                        Мне не приходилось ковыряться в js при использовании gwt :)
                        Что мне понравилось то собственно — не нужно ковыряться в ext.
                          0
                          Все правильно — при использовании чистой GWT — ковыряться в JavaScript не нужно.

                          Но GWT-Ext — это обертка вокруг ExtJs, и решать ошибки можно только путем ковыряния в JavaScript.

                          Чтобы не быть голословным:
                          1. попробуй создать CycleButton без item'ов.
                          2. попробуй создать GridPanel без колонок и данных.
                          3. аналогично любая ChartPanel.
                          4. бросить GWT-Ext Button на layout.

                          Ошибки обычно офигительно полезные…
                          com.google.gwt.core.client.JavaScriptException: (TypeError): Объект не поддерживает это свойство или метод
                          number: -2146827850
                          Все, полный конец обеда. Из Java исходников ничего не видно.
                            0
                            Еще помогает гугление. Я редко натыкаюсь на ошибки, которые не были описаны кем-то еще. Часто ответы можно найти на форуме разработчика, разве нет?
                              0
                              Да, помогает.

                              Но это не делает GWT-Ext лучше.
                              Проблемы, которые требуют гугления и спрашивания в форуме всегда будут.
                              Но до определенного уровня качества пользоваться библиотекой нельзя. В это входит в том числе документация и нормальная диагностика ошибок. Согласись, падать с дикими ошибками в Javascript — это не дело.

                        Only users with full accounts can post comments. Log in, please.