Java Platform, Enterprise Edition (Java EE) 8
Учебник по Java EE

Назад Вперёд Содержание

Приложение websocketbot

Пример приложения websocketbot, расположенного в каталоге tut-install/examples/web/websocket/websocketbot/, демонстрирует, как использовать конечную точку веб-сокета для реализации чата. Пример напоминает чат-комнату, к которой пользователи могут присоединиться и пообщаться. Пользователи могут задавать простые вопросы бот-агенту, который всегда доступен в чате.

Здесь рассматриваются следующие темы:

Архитектура приложения websocketbot

Приложение websocketbot состоит из следующих элементов:

  • Компонент CDI — Компонент CDI (BotBean), который содержит логику ответов на сообщения для бота

  • Конечная точка веб-сокета — Конечная точка веб-сокета (BotEndpoint), которая реализует комнату чата

  • Сообщения приложения — набор классов (Message, ChatMessage, InfoMessage, JoinMessage и UsersMessage), которые представляют сообщения приложения

  • Классы кодировщика — набор классов (ChatMessageEncoder, InfoMessageEncoder, JoinMessageEncoder и UsersMessageEncoder), которые кодируют сообщения приложения в текстовые сообщения вебсокета как данные JSON

  • Декодировщик сообщений — класс (MessageDecoder) анализирует текстовые сообщения веб-сокета как данные JSON и декодирует их в JoinMessage или ChatMessage объекты

  • HTML-страница — HTML-страница (index.html), которая использует код JavaScript для реализации клиента для чата

Компонент CDI

Компонент CDI (BotBean) — это класс Java, который содержит метод response. Этот метод сравнивает входящее сообщение чата с набором предварительно заданных вопросов и возвращает ответ в чате.

@Named
public class BotBean {
    public String respond(String msg) { ... }
}

Конечная точка веб-сокета

Конечная точка веб-сокета (BotEndpoint) является аннотированной конечной точкой, которая выполняет следующие функции:

  • Получает сообщения от клиентов

  • Пересылает сообщения клиентам

  • Поддерживает список подключённых клиентов

  • Вызывает функциональность бот-агента

Конечная точка указывает свой URI развёртывания, а также кодировщики и декодировщики сообщений, используя аннотацию @ServerEndpoint. Конечная точка получает объект класса BotBean и ресурс управляемого ExecutorService используя инъецирование зависимостей:

@ServerEndpoint(
   value = "/websocketbot",
   decoders = { MessageDecoder.class },
   encoders = { JoinMessageEncoder.class, ChatMessageEncoder.class,
                InfoMessageEncoder.class, UsersMessageEncoder.class }
)
/* Один объект BotEndpoint для каждого подключения */
public class BotEndpoint {
   private static final Logger logger = Logger.getLogger("BotEndpoint");
   /* функциональность бота */
   @Inject private BotBean botbean;
   /* Executor service для асинхронной обработки */
   @Resource(name="comp/DefaultManagedExecutorService")
   private ManagedExecutorService mes;

   @OnOpen
   public void openConnection(Session session) {
       logger.log(Level.INFO, "Connection opened.");
   }
   ...
}

Метод message обрабатывает входящие сообщения от клиентов. Декодировщик преобразует входящие текстовые сообщения в объекты JoinMessage или ChatMessage, которые наследуются от класса Message. Метод message получает объект Message в качестве параметра:

@OnMessage
public void message(Session session, Message msg) {
   logger.log(Level.INFO, "Received: {0}", msg.toString());

   if (msg instanceof JoinMessage) {
      /* Добавление нового пользователя и уведомление всех */
      JoinMessage jmsg = (JoinMessage) msg;
      session.getUserProperties().put("name", jmsg.getName());
      session.getUserProperties().put("active", true);
      logger.log(Level.INFO, "Received: {0}", jmsg.toString());
      sendAll(session, new InfoMessage(jmsg.getName() +
              " has joined the chat"));
      sendAll(session, new ChatMessage("Duke", jmsg.getName(),
              "Hi there!!"));
      sendAll(session, new UsersMessage(this.getUserList(session)));

   } else if (msg instanceof ChatMessage) {
      /* Отправка сообщения всем */
      ChatMessage cmsg = (ChatMessage) msg;
      logger.log(Level.INFO, "Received: {0}", cmsg.toString());
      sendAll(session, cmsg);
      if (cmsg.getTarget().compareTo("Duke") == 0) {
         /* Ответ бота на сообщение */
         mes.submit(new Runnable() {
            @Override
            public void run() {
               String resp = botbean.respond(cmsg.getMessage());
               sendAll(session, new ChatMessage("Duke",
                       cmsg.getName(), resp));
            }
         });
      }
   }
}

Если сообщение является сообщением о присоединении, конечная точка добавляет нового пользователя в список и уведомляет всех подключённых клиентов. Если сообщение является сообщением чата, конечная точка пересылает его всем подключённым клиентам.

Если сообщение чата предназначено для агента бота, конечная точка получает ответ, используя объект BotBean, и отправляет его всем подключённым клиентам. Метод sendAll аналогичен примеру в Отправка сообщений всем узлам, подключённым к конечной точке.

Асинхронная обработка и параллелизм

Конечная точка веб-сокета вызывает метод BotBean.respond для получения ответа от бота. В этом примере это операция блокировки. Пользователь, который отправил соответствующее сообщение, не сможет отправлять или получать другие сообщения чата, пока операция не завершится. Чтобы избежать этой проблемы, конечная точка получает ExecutorService из контейнера и выполняет операцию блокировки в другом потоке, используя метод ManagedExecutorService.submit утилит параллелизма в Java EE.

Спецификация Java API для веб-сокетов требует, чтобы реализации Java EE создавали объекты классов конечных точек один раз для каждого соединения. Это облегчает разработку конечных точек веб-сокетов, потому что гарантированно только один поток выполняет код в классе конечных точек веб-сокетов в любой момент времени. Когда вы вводите новый поток в конечную точку, как в этом примере, вы должны убедиться, что переменные и методы, к которым обращается более одного потока, являются потокобезопасными. В этом примере код в BotBean является потокобезопасным, а метод BotEndpoint.sendAll объявлен как synchronized.

Обратитесь к главе 59 «Утилиты параллелизма в Java EE» для получения дополнительной информации об управляемом ExecutorService и утилитах параллелизма в Java EE.

Сообщения приложения

Классы, представляющие сообщения приложения (Message, ChatMessage, InfoMessage, JoinMessage и UsersMessage) содержат только свойства и get- и set-методы. Например, класс ChatMessage выглядит следующим образом:

public class ChatMessage extends Message {
    private String name;
    private String target;
    private String message;
    /* ... Конструктор, геттеры и сеттеры ... */
}

Классы кодировщика

Классы кодировщика преобразуют объекты сообщений приложения в текст JSON, используя Java API для обработки JSON. Например, класс ChatMessageEncoder реализован следующим образом:

/* Кодировать сообщение ChatMessage как JSON.
 * Например, (new ChatMessage("Peter","Duke","How are you?"))
 * будет преобразовано к виду:
 * {"type":"chat","target":"Duke","message":"How are you?"}
 */
public class ChatMessageEncoder implements Encoder.Text<ChatMessage> {
   @Override
   public void init(EndpointConfig ec) { }
   @Override
   public void destroy() { }
   @Override
   public String encode(ChatMessage chatMessage) throws EncodeException {
      // Чтение свойств chatMessage и запись текста JSON...
   }
}

Смотрите главу 20 Обработка JSON для получения дополнительной информации об API Java для обработки JSON.

Декодировщик сообщений

Класс декодировщика сообщений (MessageDecoder) преобразует текстовые сообщения веб-сокета в сообщения приложения путём парсинга JSON. Это реализовано следующим образом:

/* Декодировать сообщение JSON в JoinMessage или ChatMessage.
 * Например, входящее сообщение
 * {"type":"chat","name":"Peter","target":"Duke","message":"How are you?"}
 * будет преобразовано к (new ChatMessage("Peter", "Duke", "How are you?"))
 */
public class MessageDecoder implements Decoder.Text<Message> {
    /* Сохранение данных из JSON в отображение в виде пар ключ-значение */
    private Map<String,String> messageMap;

    @Override
    public void init(EndpointConfig ec) { }
    @Override
    public void destroy() { }

    /* Создание нового объекта Message, если это возможно */
    @Override
    public Message decode(String string) throws DecodeException {
       Message msg = null;
       if (willDecode(string)) {
          switch (messageMap.get("type")) {
             case "join":
                msg = new JoinMessage(messageMap.get("name"));
                break;
             case "chat":
                msg = new ChatMessage(messageMap.get("name"),
                                      messageMap.get("target"),
                                      messageMap.get("message"));
          }
       } else {
          throw new DecodeException(string, "[Message] Can't decode.");
       }
       return msg;
   }

   /* Преобразование сообщения JSON в отображение и проверка,
    * что присутствуют все необходимые поля. */
   @Override
   public boolean willDecode(String string) {
      // Конвертация данных их строки в отображение ключ-значение...
      // Проверка наличия всех необходимых для этого типа сообщения полей...
   }
}

HTML-страница

Страница HTML (index.html) содержит поле для имени пользователя. После того, как пользователь вводит имя и кликает кнопку «Присоединиться», доступны три текстовые области: одна для ввода и отправки сообщений, одна для чата и одна со списком пользователей. Страница также содержит консоль веб-сокета, которая показывает сообщения, отправленные и полученные в виде текста JSON.

Код JavaScript на странице использует API веб-сокетов для подключения к конечной точке, отправки сообщений и назначения Callback-методов. API веб-сокетов поддерживается большинством современных браузеров и широко используется для разработки веб-клиентов на HTML5.

Запуск приложения websocketbot

В этом разделе описывается, как запустить пример websocketbot в IDE NetBeans и из командной строки.

Здесь рассматриваются следующие темы:

Запуск приложения websocketbot с IDE NetBeans

  1. Удостоверьтесь, чтобы GlassFish Server был запущен (см. Запуск и остановка сервера GlassFish).

  2. В меню «Файл» выберите «Открыть проект».

  3. В диалоговом окне «Открыть проект» перейдите к:

    tut-install/examples/web/websocket
  4. Выберите каталог websocketbot.

  5. Нажмите Открыть проект.

  6. На вкладке «Проекты» кликните правой кнопкой мыши проект websocketbot и выберите «Выполнить».

    Эта команда собирает и упаковывает приложение в WAR-файл, websocketbot.war, расположенный в каталоге target/, развёртывает его на сервере и запускает окно веб-браузера со следующим URL:

    http://localhost:8080/websocketbot/

    См. Тестирование приложения websocketbot для получения дополнительной информации.

Запуск приложения websocketbot с использованием Maven

  1. Удостоверьтесь, чтобы GlassFish Server был запущен (см. Запуск и остановка сервера GlassFish).

  2. В окне терминала перейдите в:

    tut-install/examples/web/websocket/websocketbot/
  3. Введите следующую команду для развёртывания приложения:

    mvn install
  4. Откройте окно веб-браузера и введите следующий URL:

    http://localhost:8080/websocketbot/

    См. Тестирование приложения websocketbot для получения дополнительной информации.

Тестирование приложения websocketbot

  1. На главной странице введите своё имя в первом текстовом поле и нажмите клавишу Enter.

    Список подключённых пользователей отображается в текстовой области справа. Текстовая область слева — комната чата.

  2. Введите сообщение в текстовой области под кнопкой входа в систему. Например, введите сообщения, как показано ниже, и нажмите клавишу Enter, чтобы получить ответы, подобные следующим:

    [--Peter has joined the chat--]
    Duke: @Peter Hi there!!
    Peter: @Duke how are you?
    Duke: @Peter I'm doing great, thank you!
    Peter: @Duke when is your birthday?
    Duke: @Peter My birthday is on May 23rd. Thanks for asking!
  3. Присоединитесь к чату из другого окна браузера, скопировав и вставив URI в адресную строку и присоединившись под другим именем.

    Новое имя пользователя появится в списке пользователей в обоих окнах браузера. Вы можете отправлять сообщения из любого окна и видеть, как они появляются в другом.

  4. Нажмите Показать консоль веб-сокета.

    Консоль показывает сообщения, отправленные и полученные в виде текста JSON.


Назад Вперёд Содержание
Логотип Oracle  Copyright © 2017, Oracle и/или её дочерних компаний. Все права защищены. Версия перевода 1.0.5 (Java EE Tutorial — русскоязычная версия)