建站不止于增删改查,还有很多很有魅力的地方。对于通信聊天这块已经青睐好久了,前段时间在做的j2ee项目运用到Spring+SpringMVC+MyBatis的框架集合,是关于一个社交平台的网站,类似于facebook,twitter,微博等。在做完基本的CURD(例如评论模块)后,开始研究网站通信并应用于项目中。
提到通信,大家都知道Socket。确实,运用Socket能在服务器与客户端之间建立一个数据交换的通道。之前用java SE写过的Socket通信 —模拟用户登录简单地实现了服务器与客户端传送消息。但是再细想一下,如果要在项目中实现网页聊天功能,把Socket用到j2ee项目中,或许就没那么简单了。这时转向baidu与google寻找答案,原来,有WebSocket这套协议,关于WebSocket,来自IBM这两篇文章已经介绍地很详细了:WebSocket 实战,使用 HTML5 WebSocket 构建实时 Web 应用。
Spring Framework 4 includes a new spring-websocket module with comprehensive WebSocket support. It is compatible with the Java WebSocket API standard (JSR-356) and also provides additional value-add as explained in the rest of the introduction.来自Spring官方文档:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html
非常庆幸的是,在Spring 4.0以上开始支持WebSocket了,并给出一套API供开发者使用。
下面就开始讲解WebSocket如何应用于SSM框架,说明其中的工作原理,并在最后给出网页聊天效果图。
一:客户端(js)新建WebSocket对象,指定要进行握手连接的服务器地址:
var webSocket = new WebSocket("ws://"+socketPath+"/ws");
webSocket.onopen = function(event){
console.log("连接成功");
console.log(event);
};
webSocket.onerror = function(event){
console.log("连接失败");
console.log(event);
};
webSocket.onclose = function(event){
console.log("Socket连接断开");
console.log(event);
};
webSocket.onmessage = function(event){
//接受来自服务器的消息
//...
}
讲解:
二:服务端导入Spring WebSocket相关jar依赖:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-messagingartifactId>
<version>4.0.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-websocketartifactId>
<version>4.0.5.RELEASEversion>
dependency>
<dependency>
<groupId>com.google.code.gsongroupId>
<artifactId>gsonartifactId>
<version>2.3.1version>
dependency>
讲解:
三:服务器添加WebSocket服务:
package web.webSocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* Component注解告诉SpringMVC该类是一个SpringIOC容器下管理的类
* 其实@Controller, @Service, @Repository是@Component的细化
*/
@Component
@EnableWebSocket
public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
@Autowired
MyWebSocketHandler handler;
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
//添加websocket处理器,添加握手拦截器
webSocketHandlerRegistry.addHandler(handler, "/ws").addInterceptors(new MyHandShakeInterceptor());
//添加websocket处理器,添加握手拦截器
webSocketHandlerRegistry.addHandler(handler, "/ws/sockjs").addInterceptors(new MyHandShakeInterceptor()).withSockJS();
}
}
讲解:
四:握手拦截器MyHandShakeInterceptor:
package web.webSocket;
import entity.User;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import javax.servlet.http.HttpSession;
import java.util.Map;
/**
* websocket握手拦截器
* 拦截握手前,握手后的两个切面
*/
public class MyHandShakeInterceptor implements HandshakeInterceptor {
public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map map) throws Exception {
System.out.println("Websocket:用户[ID:" + ((ServletServerHttpRequest) serverHttpRequest).getServletRequest().getSession(false).getAttribute("user") + "]已经建立连接");
if (serverHttpRequest instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) serverHttpRequest;
HttpSession session = servletRequest.getServletRequest().getSession(false);
// 标记用户
User user = (User) session.getAttribute("user");
if(user!=null){
map.put("uid", user.getUserId());//为服务器创建WebSocketSession做准备
System.out.println("用户id:"+user.getUserId()+" 被加入");
}else{
System.out.println("user为空");
return false;
}
}
return true;
}
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
}
}
讲解:
五:MyWebSocketHandler,WebSocket处理器:
package web.webSocket;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import entity.Message;
import service.youandmeService;
@Component
public class MyWebSocketHandler implements WebSocketHandler{
@Autowired
private youandmeService youandmeService;
//当MyWebSocketHandler类被加载时就会创建该Map,随类而生
public static final Map userSocketSessionMap;
static {
userSocketSessionMap = new HashMap();
}
//握手实现连接后
public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
int uid = (Integer) webSocketSession.getAttributes().get("uid");
if (userSocketSessionMap.get(uid) == null) {
userSocketSessionMap.put(uid, webSocketSession);
}
}
//发送信息前的处理
public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage> webSocketMessage) throws Exception {
if(webSocketMessage.getPayloadLength()==0)return;
//得到Socket通道中的数据并转化为Message对象
Message msg=new Gson().fromJson(webSocketMessage.getPayload().toString(),Message.class);
Timestamp now = new Timestamp(System.currentTimeMillis());
msg.setMessageDate(now);
//将信息保存至数据库
youandmeService.addMessage(msg.getFromId(),msg.getFromName(),msg.getToId(),msg.getMessageText(),msg.getMessageDate());
//发送Socket信息
sendMessageToUser(msg.getToId(), new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
}
public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
}
/**
* 在此刷新页面就相当于断开WebSocket连接,原本在静态变量userSocketSessionMap中的
* WebSocketSession会变成关闭状态(close),但是刷新后的第二次连接服务器创建的
* 新WebSocketSession(open状态)又不会加入到userSocketSessionMap中,所以这样就无法发送消息
* 因此应当在关闭连接这个切面增加去除userSocketSessionMap中当前处于close状态的WebSocketSession,
* 让新创建的WebSocketSession(open状态)可以加入到userSocketSessionMap中
* @param webSocketSession
* @param closeStatus
* @throws Exception
*/
public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
System.out.println("WebSocket:"+webSocketSession.getAttributes().get("uid")+"close connection");
Iterator> iterator = userSocketSessionMap.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry entry = iterator.next();
if(entry.getValue().getAttributes().get("uid")==webSocketSession.getAttributes().get("uid")){
userSocketSessionMap.remove(webSocketSession.getAttributes().get("uid"));
System.out.println("WebSocket in staticMap:" + webSocketSession.getAttributes().get("uid") + "removed");
}
}
}
public boolean supportsPartialMessages() {
return false;
}
//发送信息的实现
public void sendMessageToUser(int uid, TextMessage message)
throws IOException {
WebSocketSession session = userSocketSessionMap.get(uid);
if (session != null && session.isOpen()) {
session.sendMessage(message);
}
}
}
讲解:
六:客户端发送信息与接受信息:
发送:
var data = {};//新建data对象,并规定属性名与相应的值
data['fromId'] = sendUid;
data['fromName'] = sendName;
data['toId'] = to;
data['messageText'] = $(".contactDivTrue_right_input").val();
webSocket.send(JSON.stringify(data));//将对象封装成JSON后发送至服务器
接收:
var message = JSON.parse(event.data);//将数据解析成JSON形式
讲解:
七:Message类:
package entity;
import java.sql.Timestamp;
import java.util.Date;
/**
* Created by Administrator on 2016/8/15.
*/
public class Message {
private int messageId;
private int fromId;
private String fromName;
private int toId;
private String messageText;
private Timestamp messageDate;
public Message() {
}
public int getMessageId() {
return messageId;
}
public void setMessageId(int messageId) {
this.messageId = messageId;
}
public int getFromId() {
return fromId;
}
public void setFromId(int fromId) {
this.fromId = fromId;
}
public String getFromName() {
return fromName;
}
public void setFromName(String fromName) {
this.fromName = fromName;
}
public int getToId() {
return toId;
}
public void setToId(int toId) {
this.toId = toId;
}
public String getMessageText() {
return messageText;
}
public void setMessageText(String messageText) {
this.messageText = messageText;
}
public Timestamp getMessageDate() {
return messageDate;
}
public void setMessageDate(Timestamp messageDate) {
this.messageDate = messageDate;
}
@Override
public String toString() {
return "Message{" +
"messageId=" + messageId +
", fromId=" + fromId +
", fromName='" + fromName + '\'' +
", toId=" + toId +
", messageText='" + messageText + '\'' +
", messageDate=" + messageDate +
'}';
}
}
写到这里,如何在SSM中运用WebSocket基本已经讲完了,接下来就是实战了。这里没有给出具体github代码包。原因有二:
最后给出一些项目的WebSocket聊天实现图:
杨千嬅跟我是黄复贵的聊天:
Hill跟我是黄复贵的聊天:
我是黄复贵同时跟杨千嬅,Hill聊天:
如有问题或补充,请大家在评论中尽管提,共同交流^~^。