Java EE 7已经在今年正式发布了,新增加了很多新的功能和特性,如新增或更新了不少的JSR标准。其中特别受到关注的是Websockets。它的一个好处之一是减少了不必要的网络流量。它主要是用于在客户机和服务器之间建立单一的双向连接。这意味着客户只需要发送一个请求到服务端,那么服务端则会进行处理,处理好后则将其返回给客户端,客户端则可以在等待这个时间继续去做其他工作,整个过程是异步的。在本系列教程中,将指导用户如何在JAVA EE 7的容器GlassFish 4中,使用JAVA EE 7中的全新的解析Json API(JSR-353),以及综合运用jQuery和Bootstrap。本文要求读者有一定的HTML 5 Websocket的基础原理知识。
效果图
我们先来看下在完成这个教程后的效果图,如下所示:
准备工作
我们使用的是JDK 7 和MAVN 3进行库的构建工作,首先看pom.xml中关于Jave EE 7的部分:
${project.build.directory}/endorsed
UTF-8
javax
javaee-api
7.0
provided
org.apache.maven.plugins
maven-compiler-plugin
3.1
1.7
1.7
${endorsed.dir}
org.apache.maven.plugins
maven-war-plugin
2.3
false
org.apache.maven.plugins
maven-dependency-plugin
2.6
[..]
同时,为了能使用GlassFish 4,需要增加如下的插件:
plugin>
org.glassfish.embedded
maven-embedded-glassfish-plugin
4.0
embedded-glassfish
${basedir}/target/${project.artifactId}-${project.version}.war
true
8080
${project.artifactId}
hascode
deploy
设置Websocket的Endpoint
我们先来看服务端Websocket的代码如下,然后再做进一步解析:
packagecom.hascode.tutorial;
importjava.io.IOException;
importjava.util.logging.Level;
importjava.util.logging.Logger;
importjavax.websocket.EncodeException;
importjavax.websocket.OnMessage;
importjavax.websocket.OnOpen;
importjavax.websocket.Session;
importjavax.websocket.server.PathParam;
importjavax.websocket.server.ServerEndpoint;
@ServerEndpoint(value ="/chat/{room}", encoders = ChatMessageEncoder.class, decoders = ChatMessageDecoder.class)
publicclassChatEndpoint {
privatefinalLogger log = Logger.getLogger(getClass().getName());
@OnOpen
publicvoidopen(finalSession session,@PathParam("room")finalString room) {
log.info("session openend and bound to room: "+ room);
session.getUserProperties().put("room", room);
}
@OnMessage
publicvoidonMessage(finalSession session,finalChatMessage chatMessage) {
String room = (String) session.getUserProperties().get("room");
try{
for(Session s : session.getOpenSessions()) {
if(s.isOpen()
&& room.equals(s.getUserProperties().get("room"))) {
s.getBasicRemote().sendObject(chatMessage);
}
}
} catch(IOException | EncodeException e) {
log.log(Level.WARNING, "onMessage failed", e);
}
}
}
面分析下上面的代码:
使用@ ServerEndpoint定义一个新的endpoint,其中的值指定了URL并且可以使用PathParams参数,就象在JAX-RS中的用法一样。
所以值“/chat/{room}”允许用户通过如下形式的URL去连接某个聊天室:ws://0.0.0.0:8080/hascode/chat/java
在大括号中的值(即room),可以通过使用javax.websocket.server.PathParam,在endpoint的生命周期回调方法中以参数的方式注入。
此外,我们要使用一个编码和解码的类,因为我们使用的是一个DTO形式的类,用于在服务端和客户端传送数据。
当用户第一次连接到服务端,输入要进入聊天室的房号,则这个房号以参数的方式注入提交,并且使用session.getUserProperties将值保存在用户的属性map中。
当一个聊天参与者通过tcp连接发送信息到服务端,则循环遍历所有已打开的session,每个session被绑定到指定的聊天室中,并且接收编码和解码的信息。
如果我们想发送简单的文本信息或和二进制格式的信息,则可以使用session.getBasicRemote().sendBinary() 或session.getBasicRemote().sendText()
接下来我们看下用于代表信息传递实体(DTO:Data Transfer Object)的代码,如下:
packagecom.hascode.tutorial;
importjava.util.Date;
publicclassChatMessage {
privateString message;
privateString sender;
privateDate received;
// 其他getter,setter方法
}
聊天消息的转换
在这个应用中,将编写一个编码和解码类,用于在聊天信息和JSON格式间进行转换。
先来看下解码类的实现,这将会把传递到服务端的聊天信息转换为ChatMessage实体类。在这里,使用的是Java API for JSON Processing(JSR353)(参考:
http://jcp.org/en/jsr/detail?id=353)规范去将JSON格式的信息转换为实体类,代码如下,其中重写的willDecode方法,这里默认返回为true。
packagecom.hascode.tutorial;
importjava.io.StringReader;
importjava.util.Date;
importjavax.json.Json;
importjavax.json.JsonObject;
importjavax.websocket.DecodeException;
importjavax.websocket.Decoder;
importjavax.websocket.EndpointConfig;
publicclassChatMessageDecoderimplementsDecoder.Text {
@Override
publicvoidinit(finalEndpointConfig config) {
}
@Override
publicvoiddestroy() {
}
@Override
publicChatMessage decode(finalString textMessage)throwsDecodeException {
ChatMessage chatMessage = newChatMessage();
JsonObject obj = Json.createReader(newStringReader(textMessage))
.readObject();
chatMessage.setMessage(obj.getString("message"));
chatMessage.setSender(obj.getString("sender"));
chatMessage.setReceived(newDate());
returnchatMessage;
}
@Override
publicbooleanwillDecode(finalString s) {
returntrue;
}
}
同样再看下编码类的代码,这个类相反,是将ChatMessage类转换为Json格式,代码如下:
packagecom.hascode.tutorial;
importjavax.json.Json;
importjavax.websocket.EncodeException;
importjavax.websocket.Encoder;
importjavax.websocket.EndpointConfig;
publicclassChatMessageEncoderimplementsEncoder.Text {
@Override
publicvoidinit(finalEndpointConfig config) {
}
@Override
publicvoiddestroy() {
}
@Override
publicString encode(finalChatMessage chatMessage)throwsEncodeException {
returnJson.createObjectBuilder()
.add("message", chatMessage.getMessage())
.add("sender", chatMessage.getSender())
.add("received", chatMessage.getReceived().toString()).build()
.toString();
}
}
这里可以看到JSR-353的强大威力,只需要调用Json.createObjectBuilder就可以轻易把一个DTO对象转化为JSON了。
通过Bootstrap、Javacsript搭建简易客户端
最后,我们综合运用著名的Bootstrap、jQuery框架和Javascript设计一个简易的客户端。我们在src/main/weapp目录下新建立index.html文件,代码如下:
html>
[..]
var wsocket;
var serviceLocation="ws://0.0.0.0:8080/hascode/chat/";
var $nickName;
var $message;
var $chatWindow;
var room='';
function onMessageReceived(evt) {
//var msg=eval('(' + evt.data + ')');
var msg=JSON.parse(evt.data); // native API
var $messageLine= $('
' + msg.received+ '' + msg.sender
+ '' + msg.message
+ '
');$chatWindow.append($messageLine);
}
function sendMessage() {
var msg='{"message":"'+ $message.val() + '", "sender":"'
+ $nickName.val() + '", "received":""}';
wsocket.send(msg);
$message.val('').focus();
}
function connectToChatserver() {
room= $('#chatroom option:selected').val();
wsocket=newWebSocket(serviceLocation + room);
wsocket.onmessage=onMessageReceived;
}
function leaveRoom() {
wsocket.close();
$chatWindow.empty();
$('.chat-wrapper').hide();
$('.chat-signin').show();
$nickName.focus();
}
$(document).ready(function() {
$nickName= $('#nickname');
$message= $('#message');
$chatWindow= $('#response');
$('.chat-wrapper').hide();
$nickName.focus();
$('#enterRoom').click(function(evt) {
evt.preventDefault();
connectToChatserver();
$('.chat-wrapper h2').text('Chat # '+$nickName.val() + "@" + room);
$('.chat-signin').hide();
$('.chat-wrapper').show();
$message.focus();
});
$('#do-chat').submit(function(evt) {
evt.preventDefault();
sendMessage()
});
$('#leave-room').click(function(){
leaveRoom();
});
});
Chat sign in
Nickname
class="input-block-level"placeholder="Nickname"id="nickname">
Chatroom
id="chatroom">
arduino
java
groovy
scala