前言
在现代Web应用程序中,实时性和即时通信变得越来越重要。传统的HTTP请求-响应模式无法满足实时数据传输和双向通信的需求。随着技术的发展,WebSocket成为了一种强大而灵活的解决方案。 WebSocket是HTML5提供的一种新的通信协议,它通过一个持久化的、双向的连接,在客户端和服务器之间实现了实时的数据传输和双向通信。相比传统的HTTP请求-响应模式,WebSocket能够提供更低的延迟、更高的效率和更好的用户体验。 本文是“WebSocket 教程”系列的第三篇,将介绍如何将WebSocket与SpringMVC框架整合起来,以实现基于WebSocket的实时通信功能。我们将使用SpringMVC作为Web应用程序的基础框架,利用其强大的功能和易用的开发模式,结合WebSocket的优势,构建一个实时通信的应用程序。
一、WebSocket和SpringMVC的基本概念和原理
WebSocket是HTML5提供的一种通信协议,它允许在客户端和服务器之间建立一个持久化的、双向的连接,实现实时的数据传输和双向通信。相比传统的HTTP请求-响应模式,WebSocket具有以下特点:
双向通信:WebSocket允许客户端和服务器之间进行双向通信,而不仅仅是单向的请求-响应模式。这意味着服务器可以主动发送消息给客户端,而不需要客户端发起请求。
持久连接:一旦WebSocket连接建立成功,它将保持持久性的连接状态,不像HTTP请求-响应模式那样需要在每次请求后关闭连接。这样可以减少因频繁建立和关闭连接而产生的额外开销。
低延迟:由于WebSocket使用了长连接,数据传输的延迟相对较低。这使得实时性要求高的应用场景,如聊天应用、股票行情等,能够更好地满足用户的需求。
SpringMVC是一个基于Java的开源MVC(Model-View-Controller)框架,用于构建Web应用程序。它提供了强大的功能和灵活的开发模式,使得开发者可以轻松构建可扩展、可维护的Web应用程序。在整合WebSocket时,SpringMVC提供了对WebSocket的支持,简化了WebSocket的使用和管理。
SpringMVC框架中与WebSocket相关的几个核心概念包括:
WebSocketHandler:WebSocketHandler是一个处理WebSocket连接和消息的接口,开发者需要实现该接口来处理不同类型的WebSocket消息。
WebSocketSession:WebSocketSession代表一个WebSocket连接,它提供了一些方法来发送消息、关闭连接等操作。
WebSocketConfigurer:WebSocketConfigurer是一个配置接口,用于配置WebSocket相关的参数和处理器。
@EnableWebSocket:@EnableWebSocket注解用于启用WebSocket支持,将WebSocketConfigurer实例注册到SpringMVC的配置中。
通过使用SpringMVC提供的WebSocket支持,我们可以更方便地使用WebSocket,并且与SpringMVC的其他功能进行无缝整合。这使得开发者能够更加高效地构建实时通信的Web应用程
二、前期准备
1、新建项目,结构如下
2、导入依赖
<!-- websocket 依赖 -->
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<!-- 打印日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.3.8</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!-- ch03 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.23</version>
</dependency>
<!-- 在 spring 4.0 开始支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>5.3.23</version>
</dependency>
这些依赖项在Spring MVC项目中具有以下用途:
javax.websocket-api:这个依赖项提供了WebSocket API的规范,包括用于创建和管理WebSocket连接的接口和类。它是WebSocket开发的基本依赖项。
logback-classic:这是Logback日志框架的经典模块,用于记录应用程序的日志。它可以在开发过程中帮助调试和跟踪问题。
lombok:Lombok是一个Java库,通过注解的方式简化了Java代码的编写。它提供了各种实用的注解,如@Data、@Getter、@Setter等,可以减少样板代码的编写。
spring-webmvc:这是Spring MVC框架的核心模块,提供了处理Web请求的基本功能,包括控制器、视图解析器、数据绑定等。
spring-websocket:这是Spring框架提供的WebSocket支持模块。它为Spring MVC应用程序启用了WebSocket功能,包括配置和管理WebSocket连接、处理WebSocket消息等。
以上是这些依赖项的常见用途。通过添加这些依赖项,您可以在Spring MVC项目中使用WebSocket,并处理来自客户端的实时消息。
三、在SpringMVC中配置和启用WebSocket支持
1、WebSocket 配置类
@Configuration
// 启用 WebSocket 支持
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
/**
* 装配服务端
* @return
*/
@Bean
public WebSocketHandler webSocketHandler(){
return new ChatServer();
}
/**
* 装配 HttpSession 的拦截器,这样就可以在握手阶段
* 获取 HttpSession 的内容,在使用 WebSocketSession 时
* 就能直接得到 HttpSession 的数据
* @return
*/
@Bean
public HandshakeInterceptor handshakeInterceptor(){
return new HttpSessionHandshakeInterceptor();
}
/**
* 给服务端注册请求的端点(映射连接地址)
* @param registry
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 给 CharServer 设置连接的 url
registry.addHandler(webSocketHandler(), "/connect")
// 设置握手拦截器
.addInterceptors(handshakeInterceptor());
}
}
WebSocket的配置类,用于启用WebSocket支持并配置WebSocket相关的参数。
首先,我们使用
@Configuration
注解将该类标记为配置类。然后,我们使用
@EnableWebSocket
注解启用WebSocket支持。接下来,我们实现
WebSocketConfigurer
接口,并实现其中的方法。在
registerWebSocketHandlers()
方法中,我们通过registry.addHandler()
方法注册一个WebSocket处理器(ChatServer
)并指定连接的URL为"/connect"。同时,我们通过addInterceptors()
方法添加一个握手拦截器(HttpSessionHandshakeInterceptor
),这样就可以在握手阶段获取HttpSession的内容。最后,我们使用
@Bean
注解分别装配了ChatServer
和HttpSessionHandshakeInterceptor
的实例,并将其返回。
2、MvcConfig 配置类
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
MVC的配置类,用于启用Spring MVC并配置默认的Servlet处理。
首先,我们使用
@Configuration
注解将该类标记为配置类。然后,我们使用
@EnableWebMvc
注解启用Spring MVC支持。接下来,我们实现了
WebMvcConfigurer
接口,并重写了其中的configureDefaultServletHandling()
方法。在
configureDefaultServletHandling()
方法中,我们通过调用configurer.enable()
方法启用默认的Servlet处理。这意味着将会使用Servlet容器提供的默认Servlet来处理静态资源,而不是通过DispatcherServlet来处理。通常情况下,Spring MVC会使用DispatcherServlet作为前端控制器,负责处理所有的请求和响应。但有时候我们可能希望使用Servlet容器提供的默认Servlet来处理一些静态资源,例如图片、样式表和JavaScript文件等。通过调用
configurer.enable()
方法,我们可以启用默认的Servlet处理来处理这些静态资源。
3、AppConfig 配置类
@Configuration
@ComponentScan(basePackages = "edu.nf.ch03")
@Import({MvcConfig.class,WebSocketConfig.class})
public class AppConfig {
}
首先,我们使用
@Configuration
注解将该类标记为配置类。然后,我们使用
@ComponentScan
注解指定要扫描的基础包路径为edu.nf.ch03
。这意味着Spring将会自动扫描该包及其子包下的所有组件,例如控制器、服务类、存储库等,并进行相应的装配和管理。接下来,我们使用
@Import
注解导入了两个配置类:MvcConfig
和WebSocketConfig
。通过将其他配置类导入到当前配置类中,可以实现不同配置之间的组合和共享。最后,我们定义了一个
AppConfig
类作为整个应用的配置入口点。
4、WebConfig 配置类
public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{AppConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
这段代码是一个Web应用的配置类,用于初始化DispatcherServlet并指定配置类和URL映射。
首先,我们定义了一个名为
WebConfig
的类,并继承自AbstractAnnotationConfigDispatcherServletInitializer
类。这是Spring提供的一个方便的初始化器类,用于在Java配置中使用注解配置DispatcherServlet。然后,我们重写了
getRootConfigClasses()
方法,返回一个空数组。这意味着我们不需要在根应用上下文中加载其他的配置类,因为我们已经在getServletConfigClasses()
方法中指定了要加载的配置类。接下来,我们重写了
getServletConfigClasses()
方法,返回一个包含AppConfig.class
的数组。这样就指定了要在DispatcherServlet的应用上下文中加载的配置类,即AppConfig
。最后,我们重写了
getServletMappings()
方法,返回一个包含"/"
的字符串数组。这样就指定了DispatcherServlet的URL映射为根路径/
,表示所有请求都会被DispatcherServlet处理。
四、实现登录
1、新建 controller ,实现登录
@Controller
public class UserController {
/**
* 简单的用户登录
* @param userName
* @param session
* @return
*/
@PostMapping("/user/login")
public String login(String userName, HttpSession session){
// 将用户信息保存到会话作用域
session.setAttribute("user",userName);
// 重定向到聊天的首页
return "redirect:/static/chat.html";
}
}
这是一个基于Spring MVC的控制器类,用于处理用户登录请求。该类包含了一个login
方法,该方法通过@PostMapping
注解将请求映射到/user/login
路径。
参数说明:
userName
:表示用户输入的用户名。session
:表示HttpSession对象,用于保存用户信息。
方法逻辑:
- 将用户信息保存到会话作用域中,使用
session.setAttribute("user", userName)
,其中"user"
是属性名,userName
是属性值。 - 通过返回
"redirect:/static/chat.html"
实现重定向到聊天首页。
总结:该login
方法实现了用户登录的逻辑,并将用户信息保存到会话中,然后重定向到聊天首页。在实际应用中,需要进一步处理用户登录的验证、错误处理等逻辑。
2、新建一个登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户登录</h1>
<form name="f1" method="post" action="../user/login">
Name:<input type="text" name="userName"/>
<input type="submit" value="登录">
</form>
</body>
</html>
该页面包含了以下元素:
<form>
:HTML表单,用于向服务器端发送请求。
name="f1"
:表单的名称,用于JavaScript访问。method="post"
:使用POST方法提交请求。action="../user/login"
:指定表单提交的目标地址,即UserController
类中的login
方法的映射路径。<input type="text" name="userName"/>
:用于输入用户名的文本框。
type="text"
:指定输入框的类型为文本框。name="userName"
:指定输入框的名称,用于后台获取用户输入的用户名。<input type="submit" value="登录">
:用于提交表单的按钮。
type="submit"
:指定按钮的类型为提交按钮。value="登录"
:指定按钮上的文本为“登录”。
五、实现服务端发送信息
1、新建一个消息对象 Message
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Message {
private String fromUser;
private String sendTime;
private String content;
}
2、实现服务端聊天
public class ChatServer extends TextWebSocketHandler {
/**
* 用户列表
* 每一个用户连接时都会创建一个 WebSocket 对象
*/
private static final Map<String, WebSocketSession> users = new HashMap<>();
/**
* 客户端建立连接后执行的方法,等效于 onOpen 方法
* @param session
* @throws Exception
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 获取登录的用户信息
String userName = (String) session.getAttributes().get("user");
// 保存到用户列表
users.put(userName,session);
}
/**
* 接受客户端的消息,等效于 onMessage 方法
* @param session
* @param message
* @throws Exception
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 获取消息载体,也就是客户端发送的文本内容
String msgContent = message.getPayload();
// 获取发送人
String fromUser = (String) session.getAttributes().get("user");
// 发送时间
String sendTime = new SimpleDateFormat("hh:mm").format(new Date());
// 封装消息对象
Message msg = new Message(fromUser,sendTime,msgContent);
// 序列化为 JSON 字符串
String json = new ObjectMapper().writeValueAsString(msg);
// 群发给所有人
for( String userName : users.keySet()){
WebSocketSession s = users.get(userName);
// 发送消息,必须是一个 TextMessage 的对象
s.sendMessage(new TextMessage(json));
}
}
/**
* 连接关闭后执行的方法
* @param session
* @param status
* @throws Exception
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String userName = (String) session.getAttributes().get("user");
// 将用户移除在线列表
users.remove(userName);
}
}
这是一个简单的聊天服务器的示例代码,使用WebSocket来实现客户端之间的即时通信。让我们逐行解析这段代码的功能和实现细节:
Map<String, WebSocketSession> users
:用于保存用户列表,每个用户连接时都会创建一个WebSocketSession对象。
afterConnectionEstablished
方法:在客户端与服务器建立连接后执行的方法。它从WebSocketSession中获取登录的用户信息,并将其保存到用户列表中。
handleTextMessage
方法:接收客户端发送的消息并进行处理。它从WebSocketSession中获取消息内容和发送人信息,然后封装成一个Message对象,并将其序列化为JSON字符串。接着,它通过遍历用户列表,将消息发送给所有在线用户。
afterConnectionClosed
方法:在客户端与服务器断开连接后执行的方法。它从WebSocketSession中获取断开连接的用户信息,并将其从用户列表中移除。
以上就是这段代码的主要功能和实现逻辑。需要注意的是,这只是一个简单的示例,实际的聊天服务器可能还需要处理更多的业务逻辑和异常情况。此外,这段代码使用了Spring框架提供的TextWebSocketHandler类,你需要确保正确地配置Spring WebSocket相关的依赖和配置文件。
3、新建聊天页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/JQuery文件.txt.js"></script>
</head>
<body>
<h1>聊天室</h1>
<div id="msg">
<input type="text" id="message"/>
<input type="button" value="发送"/><br>
</div>
<script>
// 创建 WebSocket 对象
var ws = new WebSocket("ws://localhost:8080/connect");
// 接受服务端的信息
ws.onmessage = function (event) {
// 将消息填充到 div 中
let data = event.data;
// 将 json 字符串 转换为 json 对象
data = $.parseJSON(data);
$('#msg').append(data.fromUser + " : " + data.sendTime + "<br>");
$('#msg').append(data.content + "<br>");
}
$(function () {
$(':button').on('click',function () {
let msg = $('#message').val();
// 发送消息
ws.send(msg);
// 发送完之后清空消息框
$('#message').val('');
})
})
</script>
</body>
</html>
这是一个简单的聊天室的前端代码示例。让我们逐行解析这段代码的功能和实现细节:
<div id="msg">
:消息显示区域。
<input type="text" id="message"/>
:文本输入框,用于输入发送的消息内容。
<input type="button" value="发送"/><br>
:发送按钮。
<script>
:JavaScript脚本区域。
var ws = new WebSocket("ws://localhost:8080/connect");
:创建WebSocket对象,连接到服务器的地址是"ws://localhost:8080/connect"。
ws.onmessage
:监听WebSocket对象的onmessage事件,当接收到来自服务器的消息时执行相应的操作。
let data = event.data;
:获取接收到的消息数据。
data = $.parseJSON(data);
:将接收到的JSON字符串转换为JSON对象。
$('#msg').append(data.fromUser + " : " + data.sendTime + "<br>");
:将发送人和发送时间添加到消息显示区域。
$('#msg').append(data.content + "<br>");
:将消息内容添加到消息显示区域。
$('button').on('click',function () {...})
:监听发送按钮的点击事件。
let msg = $('#message').val();
:获取文本输入框中的消息内容。
ws.send(msg);
:通过WebSocket对象发送消息到服务器。
$('#message').val('');
:发送完消息后清空文本输入框。
以上就是这段前端代码的主要功能和实现逻辑。需要注意的是,这个示例代码使用了jQuery库来简化DOM操作和事件处理。你需要确保正确地引入jQuery库,并根据自己的实际情况修改WebSocket连接地址和其他相关代码。
4、运行效果
六、gitee案例
地址:ch03 · qiuqiu/WebSocket-study - 码云 - 开源中国 (gitee.com)