Friday, November 25, 2011

Основы GWT Event Bus

При разработке веб-приложений часто используют шаблон Model-View-Controller.

Model (Модель) - это данные и методы для работы с ними. Она реагирует на запросы и соответственно изменяет своё состояние. Модель ничего не знает о том, как данные визуализируются.

View (Представление) отвечает за визуализацию данных.

Controller (Контроллер) обеспечивает связь между Моделью и Представлением.

Взаимодействие этих компонентов можно представить так:


Часто бывают ситуации, когда необходимо, чтобы разные View взаимодействовали друг с другом. К примеру, у нас есть TableView с таблицей данных и PrintView с кнопкой PrintButton для печати отчётов. При нажатии на кнопку PrintButton необходимо собрать данные из таблицы TableView, сгенерировать отчёт и предоставить его пользователю в виде файла. View взаимодействуют посредством своих контроллеров:


Для взаимодействия контроллеры могут иметь ссылки друг на друга. Но что будет, если их будет не 2, а 20? Это приведёт к высокой связанности между классами. Намного лучше, если контроллеры будут взаимодействовать с помощью шины событий (Event Bus). Каждый контроллер сможет создавать события (Event) и обрабатывать их (с помощью Event Handler), при этом он не будет зависеть от других контроллеров:


Мы не будем писать приложение с несколькими формами, созданными по принципам MVC. Более наглядно будет создать простое приложение, использующее Event Bus.

Начнём с создания файла pom.xml:
  • добавим необходимые зависимости - gwt-servlet и gwt-user
  • для компиляции клиентской части добавим плагин gwt-maven-plugin с параметром "draftCompile" со значением "true" для уменьшения времени компиляции
  • в проекте не будет тестов, поэтому мы отключим их запуск из цикла сборки проекта. Для этого добавим плагин maven-surefire-plugin с параметром "skipTests" со значением "true"
  • для разворачивания приложения на сервере Tomcat после сборки добавим плагин tomcat-maven-plugin с необходимыми настройками

После всех манипуляций наш pom.xml будет выглядеть вот так:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>eventbusapp</groupId>
    <artifactId>eventbusapp</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>
    <name>eventbusapp</name>

    <properties>
        <gwt.version>2.4.0</gwt.version>
        <maven-surefire-plugin.version>2.10</maven-surefire-plugin.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.gwt</groupId>
            <artifactId>gwt-servlet</artifactId>
            <version>${gwt.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.gwt</groupId>
            <artifactId>gwt-user</artifactId>
            <version>${gwt.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>gwt-maven-plugin</artifactId>
                <version>${gwt.version}</version>
                <configuration>
                    <draftCompile>true</draftCompile>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven-surefire-plugin.version}</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>tomcat-maven-plugin</artifactId>
                <configuration>
                    <url>http://localhost:8080/manager</url>
                    <server>tomcat</server>
                    <path>/eventbusapp</path>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Наше приложение будет содержать два виджета - кнопку "Clear" и текстовое поле. При нажатии на кнопку содержимое поля должно очищаться.

Когда пользователь будет нажимать на кнопку "Clear", будет выбрасываться новое событие ClearEvent. Текстовое поле будет должно обработать выброшенное событие и очистить своё содержимое.

Создадим интерфейс обработчика события ClearEventHandler в пакете com.eventbusapp.client.event. Интерфейс содержит метод clear(), который должен очищать содержимое текстового поля:
package com.eventbusapp.client.event;

import com.google.gwt.event.shared.EventHandler;

public interface ClearEventHandler extends EventHandler {

    void clear(ClearEvent event);
}

Создадим класс события ClearEvent в том же пакете. Он содержит:
  • переменную TYPE, которая определяет тип события
  • метод getAssociatedType(), который возвращает тип события
  • метод dispatch(), который вызывает метод clear() обработчика события
package com.eventbusapp.client.event;

import com.google.gwt.event.shared.GwtEvent;

public class ClearEvent extends GwtEvent<ClearEventHandler> {

    public static Type<ClearEventHandler> TYPE = new Type<ClearEventHandler>();

    @Override
    public Type<ClearEventHandler> getAssociatedType() {
        return TYPE;
    }

    @Override
    protected void dispatch(ClearEventHandler handler) {
        handler.clear(this);
    }
}

Кнопка "Clear" будет реализована как класс ButtonView в пакете com.eventbusapp.client.view. Конструктор класса принимает экземпляр SimpleEventBus, который при нажатии на кнопку выбрасывает новое событие ClearEvent:
package com.eventbusapp.client.view;

import com.eventbusapp.client.event.ClearEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.ui.Button;

public class ButtonView extends Button {

    public ButtonView(final SimpleEventBus eventBus) {
        setText("Clear");

        addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                eventBus.fireEvent(new ClearEvent());
            }
        });
    }
}

Текстовое поле будет реализовано как класс TextBoxView в том же пакете. Конструктор класса тоже принимает экземпляр SimpleEventBus, но в этой ситуации он обрабатывает событие ClearEvent - реализует метод clear() и очищает содержимое поля:
package com.eventbusapp.client.view;

import com.eventbusapp.client.event.ClearEvent;
import com.eventbusapp.client.event.ClearEventHandler;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.ui.TextBox;

public class TextBoxView extends TextBox {

    public TextBoxView(SimpleEventBus eventBus) {
        eventBus.addHandler(ClearEvent.TYPE, new ClearEventHandler() {
            @Override
            public void clear(ClearEvent event) {
                setText("");
            }
        });
    }
}

Создадим главный класс приложения - EventBusApp в пакете com.eventbusapp.client. Создадим экземпляр шины событий SimpleEventBus и добавим наши кнопку и текстовое поле на страницу:
package com.eventbusapp.client;

import com.eventbusapp.client.view.ButtonView;
import com.eventbusapp.client.view.TextBoxView;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.ui.RootPanel;

public class EventBusApp implements EntryPoint {

    @Override
    public void onModuleLoad() {
        SimpleEventBus eventBus = new SimpleEventBus();

        RootPanel.get().add(new ButtonView(eventBus));
        RootPanel.get().add(new TextBoxView(eventBus));
    }
}

Создадим файл настройки модуля EventBusApp.gwt.xml в пакете com.eventbusapp. Для уменьшения времени компиляции укажем компилировать приложение только для одного браузера:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.2//EN"
        "http://google-web-toolkit.googlecode.com/svn/tags/1.6.2/distro-source/core/src/gwt-module.dtd">

<module rename-to='eventbusapp'>
    <inherits name='com.google.gwt.user.User'/>
    <inherits name='com.google.gwt.user.theme.standard.Standard'/>

    <entry-point class='com.eventbusapp.client.EventBusApp'/>

    <set-property name="user.agent" value="safari"/>
</module>

Создадим дескриптор развёртывания приложения web.xml в директории webapp/WEB-INF:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

    <welcome-file-list>
        <welcome-file>EventBusApp.html</welcome-file>
    </welcome-file-list>

</web-app>

Создадим главную страницу приложения EventBusApp.html в директории webapp:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>EventBusApp</title>
    <script type="text/javascript" language="javascript" 
            src="eventbusapp/eventbusapp.nocache.js"></script>
</head>
<body>

</body>
</html>

Запустим Tomcat, соберём и развернём приложение, выполнив команду "mvn clean tomcat:deploy":
[INFO] Scanning for projects...
[WARNING] 
[WARNING] Some problems were encountered while building the effective model for eventbusapp:eventbusapp:war:1.0
[WARNING] 'build.plugins.plugin.version' for org.codehaus.mojo:tomcat-maven-plugin is missing. @ line 55, column 21
[WARNING] 
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING] 
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING] 
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building eventbusapp 1.0
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ eventbusapp ---
[INFO] Deleting D:\Work\eventbusapp\target
[INFO] 
[INFO] >>> tomcat-maven-plugin:1.1:deploy (default-cli) @ eventbusapp >>>
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ eventbusapp ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\Work\eventbusapp\src\main\resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ eventbusapp ---
[WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
[INFO] Compiling 5 source files to D:\Work\eventbusapp\target\classes
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ eventbusapp ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\Work\eventbusapp\src\test\resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ eventbusapp ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:2.10:test (default-test) @ eventbusapp ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- gwt-maven-plugin:2.4.0:compile (default) @ eventbusapp ---
[INFO] auto discovered modules [com.eventbusapp.EventBusApp]
[INFO] Compiling module com.eventbusapp.EventBusApp
[INFO]    Compiling 1 permutation
[INFO]       Compiling permutation 0...
[INFO]    Compile of permutations succeeded
[INFO] Linking into D:\Work\eventbusapp\target\eventbusapp-1.0\eventbusapp
[INFO]    Link succeeded
[INFO]    Compilation succeeded -- 17.117s
[INFO] 
[INFO] --- maven-war-plugin:2.1.1:war (default-war) @ eventbusapp ---
[INFO] Packaging webapp
[INFO] Assembling webapp [eventbusapp] in [D:\Work\eventbusapp\target\eventbusapp-1.0]
[INFO] Processing war project
[INFO] Copying webapp resources [D:\Work\eventbusapp\src\main\webapp]
[INFO] Webapp assembled in [110 msecs]
[INFO] Building war: D:\Work\eventbusapp\target\eventbusapp-1.0.war
[WARNING] Warning: selected war files include a WEB-INF/web.xml which will be ignored 
(webxml attribute is missing from war task, or ignoreWebxml attribute is specified as 'true')
[INFO] 
[INFO] <<< tomcat-maven-plugin:1.1:deploy (default-cli) @ eventbusapp <<<
[INFO] 
[INFO] --- tomcat-maven-plugin:1.1:deploy (default-cli) @ eventbusapp ---
[INFO] Deploying war to http://localhost:8080/eventbusapp  
[INFO] OK - Deployed application at context path /eventbusapp
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 22.834s
[INFO] Finished at: Fri Nov 25 01:11:01 EET 2011
[INFO] Final Memory: 16M/309M
[INFO] ------------------------------------------------------------------------

Перейдём по ссылке http://localhost:8080/eventbusapp/. Должна отображаться страница с кнопкой "Clear" и текстовым полем. При нажатии на кнопку содержимое поля должно очищаться:











Структура проекта:































Исходный код созданного приложения доступен по ссылке https://code.google.com/p/eventbusapp/.

Ещё про основы работы с EventBus можно посмотреть в этом замечательном видео и в этой статье.


Рекомендуемые посты:

6 comments:

  1. тут, я так понял нет место паретну VMP ?

    ReplyDelete
    Replies
    1. Сергей, в данном простейшем примере в этом нет необходимости, так как View всего одна.

      Delete
  2. Это столько нужно катать что бы кнопка с инпутом появилась ? Мазохизм ...

    ReplyDelete
    Replies
    1. Для кнопки с инпутом достаточно создать один класс EventBusApp. Все остальное нужно для работы с единой шиной событий. Java никогда не отличалась лаконичностью :)

      Delete
  3. Классный блог, подписался :) А про статью спасибо, как раз нужно воспользоваться шиной событий.

    ReplyDelete