Как стать автором
Обновить

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

Время на прочтение13 мин
Количество просмотров4.3K
Когда я увидел 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.
Теги:
Хабы:
+11
Комментарии24

Публикации

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн