介绍
最新版本的JDeveloper 12c(12.1.3.0)和WebLogic Server 12.1.3一起提供了一些新的Java EE 7功能。 其中之一是对用于WebSocket的JSR 356 Java API的支持。 实际上,从12.1.2.0版本开始就支持WebSocket协议(RFC 6455),但是它基于WebSocket API的WebLogic特定实现。 现在,此专有的WebLogic Server WebSocket API已被弃用。 但是,仍支持向后兼容。
在本文中,我将展示一个在简单的ADF应用程序中为WebSocket使用JSR 356 Java API的示例。 该用例是关于在塔斯曼海发生的一些帆船赛。 有三艘船参加帆船赛,它们将穿越塔斯曼海,从澳大利亚航行至新西兰海岸。 该示例应用程序的目标是监视帆船赛,并告知用户进行情况,在地图上显示船只的位置。
我们将在应用程序中声明一个WebSocket服务器端点,当用户打开页面时,Java脚本函数将打开一个新的WebSocket连接。 该应用程序使用计划的服务,该服务每秒钟更新一次船的坐标,并向所有活动的WebSocket客户端发送一条包含新船位置的消息。 在客户端,Java脚本功能接收消息并根据GPS坐标将标记添加到Google地图。 因此,每个对帆船赛感兴趣的用户都将看到代表比赛当前状态的同一张更新图片。
WebSocket服务器端点
让我们从声明一个WebSocket服务器端点开始。 当前实施中有一个小问题,将来的发行版中可能会解决。 WebSocket端点不能与ADF页面混合使用,应将它们部署在单独的WAR文件中。 最简单的方法是在应用程序中创建一个单独的WebSocket项目,并在此项目中声明所有必需的端点:
这对于为项目设置可读的Java EE Web上下文根也很重要:
下一步是创建一个Java类,它将成为WebSocket端点。 因此,这是一个普通的类,在开始时带有特殊注释:
@ServerEndpoint(value = "/message")
public class MessageEndPoint {public MessageEndPoint() {super();}
}
注意,JDeveloper用红色标记注释。 我们将通过让JDeveloper为Web Socket配置项目来解决此问题。
完成此操作后,JDeveloper将把该项目转换为一个Web项目,并添加Web.xml文件并添加必要的库:
此外,端点类变得可运行,我们可以运行它以检查其实际工作方式:
作为响应,JDeveloper生成以下URL,在该URL上WebSocket端点可用。 请注意,URL包含项目上下文根( WebSocket )和批注的value属性( / message )。 如果一切正常,那么在单击URL时,将获得“已成功连接”信息窗口:
顺便说一句,消息中有一个错字。
现在让我们向WebSocket终结点类添加一些实现。 根据规范,将为每个WebSocket连接创建一个MessageEndPoin t类的新实例。 为了容纳所有活动的WebSocket会话,我们将使用静态队列:
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 sessionsfinal static Queue<Session> queue = new ConcurrentLinkedQueue<>(); @OnOpenpublic void open(Session session) {queue.add(session); } @OnClosepublic void closedConnection(Session session) {queue.remove(session);}@OnErrorpublic void error(Session session, Throwable t) {queue.remove(session);t.printStackTrace();}
建立新连接,关闭新连接以及发生错误时 ,将分别调用带注释的方法open , closedConnection和error 。 完成此操作后,我们可以使用一些静态方法向所有客户端广播文本消息:
public static void broadCastTex(String message) {for (Session session : queue) {try {session.getBasicRemote().sendText(message);} catch (IOException e) {e.printStackTrace();}}}
在我们的用例中,我们必须用船的新GPS坐标通知用户,因此我们应该能够通过WebSockets发送比文本消息更复杂的信息。
发送对象
基本上,示例应用程序的业务模型由两个简单的Java类Boat表示 :
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;}
...
和帆船赛 :
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;}
...
对于我们的用例,我们将向WebSocket客户端发送Regatta类的实例。 赛 船会包含以Boat类实例表示的所有赛船会参与者,其中包含更新的GPS坐标( 经度和纬度 )。
这可以通过创建Encoder.Text <Regatta>接口的自定义实现来完成,换句话说,我们将创建一个编码器,该编码器可以将Regatta实例转换为文本并指定该编码器供WebSocket使用端点,同时发送Regatta实例。
public class RegattaTextEncoder implements Encoder.Text<Regatta> {@Overridepublic void init(EndpointConfig ec) { }@Overridepublic 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;}@Overridepublic String encode(Regatta regatta) throws EncodeException {JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); for (Boat boat : regatta.getParticipants()) {arrayBuilder.add(encodeBoat(boat));}return arrayBuilder.build().toString(); }}
@ServerEndpoint(value = "/message",encoders = {RegattaTextEncoder.class })
完成后,我们可以将对象发送给我们的客户:
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使用Json表示法将Regatta对象表示为船的列表,因此它将是这样的:
[{"country":"us","longitude":151.67,"latitude":-33.84},{"country":"ca", ...},{"country":"nz", ...}]
接收讯息
在客户端,我们使用Java脚本函数来打开新的WebSocket连接:
//Open a new WebSocket connection
//Invoked on page load
function connectSocket() { websocket = new WebSocket(getWSUri()); websocket.onmessage = onMessage;
}
当消息到达时,我们将遍历一系列船只,并为每条船只在地图上添加一个标记:
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坐标,并将更新广播给所有用户:
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 regattaif (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 boatspublic void run() { regatta.move();MessageEndPoint.sendRegatta(regatta); if (regatta.getLongitude() >= FINISH_LONGITUDE) {runHandle.cancel(true); }}
}
赌你的船
最后,我们的工作结果如下所示:
本文的示例应用程序需要JDeveloper 12.1.3。 玩得开心!
而已!
翻译自: https://www.javacodegeeks.com/2014/10/using-java-api-for-websockets-in-jdeveloper-12-1-3.html