Вступление
В последней версии JDeveloper 12c (12.1.3.0) вместе с WebLogic Server 12.1.3 появилось несколько новых функций Java EE 7. Одним из них является поддержка JSR 356 Java API для WebSockets. На самом деле протокол WebSocket (RFC 6455) поддерживается начиная с версии 12.1.2.0, но он был основан на конкретной реализации WebSocket API для WebLogic. Теперь этот проприетарный API WebSocket WebLogic устарел. Тем не менее, он все еще поддерживается для обратной совместимости.
В этом посте я собираюсь показать пример использования Java API JSR 356 для WebSockets в простом приложении ADF. Вариант использования — про парусную регату, которая проходит в Тасмановом море. В регате участвуют три лодки, и они собираются пересечь Тасмановое море, плывущее от Австралии до побережья Новой Зеландии. Целью примера приложения является наблюдение за регатой и информирование пользователей о том, как это происходит, с указанием местоположения лодок на карте.
Мы собираемся объявить конечную точку сервера WebSocket в приложении, и когда пользователь открывает страницу, функция скрипта Java открывает новое соединение WebSocket. Приложение использует запланированный сервис, который каждую секунду обновляет координаты лодок и отправляет сообщение с новыми позициями лодок всем активным клиентам WebSocket. На стороне клиента функция скрипта Java получает сообщение и добавляет маркеры на карту Google в соответствии с GPS-координатами. Итак, каждый пользователь, заинтересованный в регате, увидит одно и то же обновленное изображение, отображающее текущее состояние соревнования.
Конечная точка сервера WebSocket
Начнем с объявления конечной точки сервера WebSocket. В текущей реализации есть небольшая проблема, которая, вероятно, будет решена в будущих выпусках. Конечные точки WebSocket нельзя смешивать со страницами ADF, и их следует развертывать в отдельном файле WAR. Самый простой способ сделать это — создать отдельный проект WebSocket внутри приложения и объявить все необходимые конечные точки в этом проекте:
Это также важно для настройки читаемого корня веб-контекста Java EE для проекта:
Следующим шагом является создание класса Java, который будет конечной точкой WebSocket. Итак, это обычный класс со специальной аннотацией в самом начале:
1
2
3
4
5
6
7
|
@ServerEndpoint (value = "/message" ) public class MessageEndPoint { public MessageEndPoint() { super (); } } |
Обратите внимание, что JDeveloper подчеркивает аннотацию красным цветом. Мы собираемся решить эту проблему, позволив JDeveloper настроить проект для Web Socket.
Сделав это, JDeveloper собирается преобразовать проект в веб-проект, добавив файл Web.xml и добавив необходимую библиотеку:
Кроме того, класс конечной точки становится работоспособным, и мы можем просто запустить его, чтобы проверить, как он на самом деле работает:
В ответ JDeveloper генерирует следующий URL, по которому доступна конечная точка WebSocket. Обратите внимание, что URL содержит корневой каталог контекста проекта ( WebSocket ) и свойство value аннотации ( / message ). Если все в порядке, тогда, когда мы нажмем на URL, мы получим информационное окно «Подключено успешно»:
Кстати, в сообщении есть опечатка.
А теперь давайте добавим некоторую реализацию в класс конечных точек WebSocket. Согласно спецификации, для каждого соединения WebSocket будет создан новый экземпляр класса MessageEndPoin t. Чтобы сохранить кучу всех активных сеансов WebSocket, мы будем использовать статическую очередь:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class MessageEndPoint { //A new instance of the MessageEndPoint class //is going to be created for each WebSocket connection //This queue contains all active WebSocket sessions final static Queue<Session> queue = new ConcurrentLinkedQueue<>(); @OnOpen public void open(Session session) { queue.add(session); } @OnClose public void closedConnection(Session session) { queue.remove(session); } @OnError public void error(Session session, Throwable t) { queue.remove(session); t.printStackTrace(); } |
Аннотированные методы open , closedConnection и error будут вызываться соответственно, когда будет установлено новое соединение, когда оно было закрыто и когда произошло что-то не так. Сделав это, мы можем использовать статический метод для широковещательной рассылки текстовых сообщений всем клиентам:
01
02
03
04
05
06
07
08
09
10
|
public static void broadCastTex(String message) { for (Session session : queue) { try { session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } } } |
В нашем случае мы должны уведомлять пользователей новыми GPS-координатами лодок, поэтому мы должны иметь возможность отправлять через WebSockets нечто более сложное, чем просто текстовые сообщения.
Отправка объекта
По сути, бизнес-модель примера приложения представлена двумя простыми Java-классами Boat :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public class Boat { private final String country; private final double startLongitude; private final double startLatitude; private double longitude; private double latitude; public String getCountry() { return country; } public double getLongitude() { return longitude; } public double getLatitude() { return latitude; } public Boat(String country, double longitude, double latitude) { this .country = country; this .startLongitude = longitude; this .startLatitude = latitude; } ... |
и регата :
01
02
03
04
05
06
07
08
09
10
11
|
public class Regatta { private final Boat[] participants = new Boat[] { new Boat( "us" , 151.644 , - 33.86 ), new Boat( "ca" , 151.344 , - 34.36 ), new Boat( "nz" , 151.044 , - 34.86 ) }; public Boat[] getParticipants() { return participants; } ... |
Для нашего варианта использования мы собираемся отправить экземпляр класса Regatta клиентам WebSocket. Регата содержит всех участников регаты, представленных экземплярами класса « Лодка » с обновленными GPS-координатами ( долгота и широта ).
Это может быть сделано путем создания пользовательской реализации интерфейса Encoder.Text <Regatta> или, другими словами, мы собираемся создать кодировщик, который может преобразовать экземпляр Regatta в текст и указать этот кодировщик, который будет использоваться WebSocket. конечная точка при отправке экземпляра регаты .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public class RegattaTextEncoder implements Encoder.Text<Regatta> { @Override public void init(EndpointConfig ec) { } @Override public void destroy() { } private JsonObject encodeBoat(Boat boat) throws EncodeException { JsonObject jsonBoat = Json.createObjectBuilder() .add( "country" , boat.getCountry()) .add( "longitude" , boat.getLongitude()) .add( "latitude" , boat.getLatitude()).build(); return jsonBoat; } @Override public String encode(Regatta regatta) throws EncodeException { JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); for (Boat boat : regatta.getParticipants()) { arrayBuilder.add(encodeBoat(boat)); } return arrayBuilder.build().toString(); } } |
1
2
3
|
@ServerEndpoint ( value = "/message" , encoders = {RegattaTextEncoder. class }) |
Сделав это, мы можем отправлять объекты нашим клиентам:
01
02
03
04
05
06
07
08
09
10
11
12
|
public static void sendRegatta(Regatta regatta) { for (Session session : queue) { try { session.getBasicRemote().sendObject(regatta); } catch (EncodeException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } |
RegattaTextEncoder представляет объект Regatta в виде списка лодок с использованием нотации Json, поэтому он будет примерно таким:
[{"country":"us","longitude":151.67,"latitude":-33.84},{"country":"ca", ...},{"country":"nz", ...}]
Получение сообщения
На стороне клиента мы используем функцию сценария Java для открытия нового соединения WebSocket:
1
2
3
4
5
6
|
//Open a new WebSocket connection //Invoked on page load function connectSocket() { websocket = new WebSocket(getWSUri()); websocket.onmessage = onMessage; } |
И когда приходит сообщение, мы собираемся перебрать массив лодок и для каждой лодки добавить маркер на карте:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
function onMessage(evt) { var boats = JSON.parse(evt.data); for (i= 0 ; i<boats.length; i++) { markBoat(boats[i]); } } function markBoat(boat) { var image = '../resources/images/' +boat.country+ '.png' ; var latLng = new google.maps.LatLng(boat.latitude,boat.longitude); mark = new google.maps.Marker({ position: latLng, map: map, title: boat.country, icon: image }); } |
Здесь вы можете узнать, как интегрировать карты Google в ваши приложения.
Запустить регату
Чтобы эмулировать живое шоу, мы используем ScheduledExecutorService . Каждую секунду мы собираемся обновлять координаты GPS и транслировать обновление всем подписчикам:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool( 1 ); private ScheduledFuture<?> runHandle; //Schedule a new regatta on Start button click public void startRegatta(ActionEvent actionEvent) { //Cancel the previous regatta if (runHandle != null ) { runHandle.cancel( false ); } runHandle = scheduler.scheduleAtFixedRate( new RegattaRun(), 1 , 1 , TimeUnit.SECONDS); } public class RegattaRun implements Runnable { private final static double FINISH_LONGITUDE = 18 ; private final Regatta regatta = new Regatta(); //Every second update GPS coordinates and broadcast //new positions of the boats public void run() { regatta.move(); MessageEndPoint.sendRegatta(regatta); if (regatta.getLongitude() >= FINISH_LONGITUDE) { runHandle.cancel( true ); } } } |
Ставка на свою лодку
И наконец, результат нашей работы выглядит так:
Пример приложения для этого поста требует JDeveloper 12.1.3. Повеселись!
Это оно!
Ссылка: | Использование Java API для WebSockets в JDeveloper 12.1.3 от нашего партнера по JCG Евгения Федоренко из блога ADF Practice . |