Spring框架中自带了WebSocket的jar包,利用它可以实现与H5中WebSocket进行对接,实现交互。使用Spring WebSocket并不复杂,下面一起来看下怎么实现吧(注:本例子是通过SpringBoot构建的项目,除了项目的启动代码配置不一样外,WebSocket的配置代码可在SpringMVC上无缝使用)。
这个例子会通过模拟登陆,在Session中保存用户信息,然后进行WebSocket通信时,从Session中将用户信息取出来,并将信息转发给指定用户。
一、所需POM配置
SpringMVC配置:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
通过SpringMVC来构建项目的话,除了一些基本的Spring jar包之外,只需要添加上面这个jar包即可。
SpringBoot配置:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
</dependency>
</dependencies>
使用SpringBoot来构建项目的话,需要使用上面这些jar包,上面的jar包包含了SpringBoot的配置、WebSocket的配置。
二、代码实现
启动类
@SpringBootApplication
@Controller
@ComponentScan(basePackages={"com.test.spring.boot"}) //自定义自动扫描
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
上面代码是SpringBoot的一个项目启动类,执行main方法就可以启动服务,相关配置在这篇文章里不是重点,就不写出来了,感兴趣可以看这篇文章:SpringBoot+Thymeleaf整合开发实例。
这里需要注意的是 @ComponentScan(basePackages={"com.test.spring.boot"})
,代表了扫描的包名。
访问Controller
@Controller
@RequestMapping("/")
public class HelloController {
private static final String SESSION_USER = "user";
@Autowired
private MyWebSocketHandler myWebSocketHandler;
// 访问跳转到websocket.html页面
@RequestMapping("/socket")
public String websocket() {
return "websocket";
}
// 模拟登陆操作,将用户信息存入Session
@RequestMapping("/login")
public @ResponseBody String login(UserBean userBean, HttpServletRequest request) {
System.out.println("========================== 开始登录 ===================");
System.out.println("userId="+userBean.getId());
System.out.println("userId="+userBean.getUsername());
System.out.println("userId="+userBean.getPhone());
request.getSession().setAttribute(SESSION_USER, userBean);
System.out.println("========================== 登录成功 ===================");
return "success";
}
// 普通操作,触发WebSocket通信
@RequestMapping("/send/message")
public @ResponseBody String sendMessage(HttpServletRequest request) {
UserBean userBean = (UserBean) request.getSession().getAttribute(SESSION_USER);
boolean isSuccess = myWebSocketHandler.sendMessageToUser(userBean.getId(), "测试发送消息");
System.out.println(isSuccess);
return "message";
}
}
注册WebSocket处理类
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyWebSocketHandler(), "/websocket").addInterceptors(new WebSocketInterceptor());
}
}
这是一个WebSocket配置类,实现接口来配置Websocket请求的路径和拦截器。 这里是将请求路径 /websocket
绑定到MyWebSocketHandler()
类,并设置一个拦截器WebSocketInterceptor()
,
注解不要忘掉,否则无法注册这个配置类。
WebSocket拦截器
/**
* WebSocket 拦截器,用于将用户信息从session中存入map,方便后面websocket请求时从map中找到指定的用户session信息
* @author Administrator
*
*/
public class WebSocketInterceptor implements HandshakeInterceptor {
private static final String SESSION_USER = "user";
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> map) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request;
HttpSession session = serverHttpRequest.getServletRequest().getSession();
if (session != null) {
map.put(SESSION_USER, session.getAttribute(SESSION_USER));
}
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
}
}
这个拦截器的作用是在请求WebSocket时,将Session中的用户信息转存到map中,方便后面WebSocket请求处理时,能够从map中获取到用户信息。
WebSocket具体请求处理类
在贴代码之前,我们先来看下WebSocket具体请求处理类中的那些方法可以重写,他们是什么含义:
在
afterConnectionEstablished
连接建立成功之后,记录用户的连接标识,便于后面发信息,这里我是记录将id记录在Map集合中。在
handleTextMessage
中可以对H5 Websocket的send方法进行处理。handleTransportError
连接出错处理,主要是关闭出错会话的连接,和删除在Map集合中的记录。afterConnectionClosed
连接已关闭,移除在Map集合中的记录。
@Component
public class MyWebSocketHandler extends TextWebSocketHandler {
//在线用户列表
private static final Map<Integer, WebSocketSession> users = new HashMap<Integer, WebSocketSession>();
//用户标识
private static final String SESSION_USER = "user";
/**
* 连接已关闭,移除在Map集合中的记录
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
users.remove(getUserId(session));
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
if (session.isOpen()) {
session.close();
}
System.out.println("连接出错");
users.remove(getUserId(session));
}
/**
* 连接建立成功之后,记录用户的连接标识,便于后面发信息
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("成功建立连接");
Integer userId = getUserId(session);
System.out.println(userId);
if (userId != null) {
users.put(userId, session);
session.sendMessage(new TextMessage("成功建立socket连接"));
System.out.println(userId);
System.out.println(session);
}
}
/**
* 处理收到的websocket信息
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
}
/**
* 发送信息给指定用户
* @param clientId
* @param message
* @return
*/
public boolean sendMessageToUser(Integer clientId, String message) {
if (users.get(clientId) == null) {
return false;
}
WebSocketSession session = users.get(clientId);
System.out.println("sendMessage:" + session);
if (!session.isOpen()) {
return false;
}
try {
int count = 1;
TextMessage textMessage = null;
String newMessage = "";
// 循环向客户端发送数据
while(true) {
newMessage = message + String.valueOf(count);
textMessage = new TextMessage(newMessage);
session.sendMessage(textMessage);
Thread.sleep(5000);
newMessage = "";
}
} catch (IOException e) {
e.printStackTrace();
return false;
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
}
/**
* 广播信息
* @param message
* @return
*/
public boolean sendMessageToAllUsers(TextMessage message) {
boolean allSendSuccess = true;
Set<Integer> clientIds = users.keySet();
WebSocketSession session = null;
for (Integer clientId : clientIds) {
try {
session = users.get(clientId);
if (session.isOpen()) {
session.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
allSendSuccess = false;
}
}
return allSendSuccess;
}
/**
* 获取用户标识
* @param session
* @return
*/
private Integer getUserId(WebSocketSession session) {
try {
UserBean userBean = (UserBean) session.getAttributes().get(SESSION_USER);
return userBean.getId();
} catch (Exception e) {
return null;
}
}
}
前端实现
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>WebSocket Test</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" th:href="@{/css/main.css}"/>
<script th:src="@{/js/jQuery-2.1.4.min.js}" type="text/javascript"></script>
</head>
<body>
<div>
<input type="button" id="btn_test" value="点击获取数据"/>
</div>
<div>
<div id="test_1">
</div>
</div>
</body>
<script type="text/javascript">
//<![CDATA[
$(document).ready(function() {
var websocket = null;
var sendData = {"id": 1, "username": "tanjin", "phone": "1888221322122"};
// 模拟登陆
$.post("login", sendData, function(data) {
// 初始化一个WebSocket连接
websocket = new WebSocket("ws://localhost:8082/websocket");
websocket.onerror = function(event) {onError(event);};
websocket.onopen = function(event) {onOpen(event);};
// websocket接收的信息会通过这个回调方法返回给前端
websocket.onmessage = function(event) {onMessage(event);};
})
// 打印收到的websocket返回信息
function onMessage(event) {
$("#test_1").append("<label class='navbar-label'>" + event.data + "</label>").append("</br>");
$("#test_1").scrollTop( $("#test_1")[0].scrollHeight);
}
// 开启websocket请求回调处理
function onOpen(event) {
$("#test_1").empty();
var label = $("<label>").html("开始执行....");
$("#test_1").append(label).append("<br>").append("<br>");
}
//报错处理回调函数
function onError(event) {
alert(event.data);
}
//点击页面上的一个按钮,通过一个普通方法来开始一个websocket请求
$("#btn_test").click(function() {
$.post("send/message", null, function() {})
})
})
//]]>
</script>
</html>
//<![CDATA[
这个标签在这里是由于thymeleaf的语法限制使用的,如果是JSP则不需要。